package org.checkerframework.framework.flow;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.BinaryOperator;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.dataflow.analysis.Store;
import org.checkerframework.dataflow.cfg.node.ArrayAccessNode;
import org.checkerframework.dataflow.cfg.node.FieldAccessNode;
import org.checkerframework.dataflow.cfg.node.LocalVariableNode;
import org.checkerframework.dataflow.cfg.node.MethodInvocationNode;
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.cfg.node.ThisNode;
import org.checkerframework.dataflow.cfg.visualize.CFGVisualizer;
import org.checkerframework.dataflow.cfg.visualize.StringCFGVisualizer;
import org.checkerframework.dataflow.expression.ArrayAccess;
import org.checkerframework.dataflow.expression.ClassName;
import org.checkerframework.dataflow.expression.FieldAccess;
import org.checkerframework.dataflow.expression.JavaExpression;
import org.checkerframework.dataflow.expression.LocalVariable;
import org.checkerframework.dataflow.expression.MethodCall;
import org.checkerframework.dataflow.expression.SuperReference;
import org.checkerframework.dataflow.expression.ThisReference;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.framework.qual.MonotonicQualifier;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.IPair;
import org.plumelib.util.ToStringComparator;
import org.plumelib.util.UniqueId;

/**
 * A store for the Checker Framework analysis. It tracks the annotations of memory locations such as
 * local variables and fields.
 *
 * <p>When adding a new field to track values for a code construct (similar to {@code
 * localVariableValues} and {@code thisValue}), it is important to review all constructors and
 * methods in this class for locations where the new field must be handled (such as the copy
 * constructor and {@code clearValue}), as well as all constructors/methods in subclasses of {code
 * CFAbstractStore}. Note that this includes not only overridden methods in the subclasses, but new
 * methods in the subclasses as well. Also check if
 * BaseTypeVisitor#getJavaExpressionContextFromNode(Node) needs to be updated. Failing to do so may
 * result in silent failures that are time consuming to debug.
 */
// TODO: Split this class into two parts: one that is reusable generally and
// one that is specific to the Checker Framework.
public abstract class CFAbstractStore<V extends CFAbstractValue<V>, S extends CFAbstractStore<V, S>>
    implements Store<S>, UniqueId {

  /** The analysis class this store belongs to. */
  protected final CFAbstractAnalysis<V, S, ?> analysis;

  /** Information collected about local variables (including method parameters). */
  protected final Map<LocalVariable, V> localVariableValues;

  /** Information collected about the current object. */
  protected V thisValue;

  /** Information collected about fields, using the internal representation {@link FieldAccess}. */
  protected Map<FieldAccess, V> fieldValues;

  /**
   * Returns information about fields. Clients should not side-effect the returned value, which is
   * aliased to internal state.
   *
   * @return information about fields
   */
  public Map<FieldAccess, V> getFieldValues() {
    return fieldValues;
  }

  /**
   * Information collected about array elements, using the internal representation {@link
   * ArrayAccess}.
   */
  protected final Map<ArrayAccess, V> arrayValues;

  /**
   * Information collected about the expressions to which method calls evaluate, using the internal
   * representation {@link MethodCall}.
   */
  protected final Map<MethodCall, V> methodCallExpressions;

  /**
   * Information collected about <i>classname</i>.class values, using the internal representation
   * {@link ClassName}.
   */
  protected final Map<ClassName, V> classValues;

  /**
   * Should the analysis use sequential Java semantics (i.e., assume that only one thread is running
   * at all times)?
   */
  protected final boolean sequentialSemantics;

  /** True if -AassumeSideEffectFree or -AassumePure was passed on the command line. */
  private final boolean assumeSideEffectFree;

  /** True if -AassumePureGetters was passed on the command line. */
  private final boolean assumePureGetters;

  /** The unique ID for the next-created object. */
  private static final AtomicLong nextUid = new AtomicLong(0);

  /** The unique ID of this object. */
  private final transient long uid = nextUid.getAndIncrement();

  @Override
  public long getUid() {
    return uid;
  }

  /* --------------------------------------------------------- */
  /* Initialization */
  /* --------------------------------------------------------- */

  /**
   * Creates a new CFAbstractStore.
   *
   * @param analysis the analysis class this store belongs to
   * @param sequentialSemantics should the analysis use sequential Java semantics?
   */
  protected CFAbstractStore(CFAbstractAnalysis<V, S, ?> analysis, boolean sequentialSemantics) {
    this.analysis = analysis;
    this.localVariableValues = new HashMap<>();
    this.thisValue = null;
    this.fieldValues = new HashMap<>();
    this.methodCallExpressions = new HashMap<>();
    this.arrayValues = new HashMap<>();
    this.classValues = new HashMap<>();
    this.sequentialSemantics = sequentialSemantics;
    this.assumeSideEffectFree =
        analysis.checker.hasOption("assumeSideEffectFree")
            || analysis.checker.hasOption("assumePure");
    this.assumePureGetters = analysis.checker.hasOption("assumePureGetters");
  }

  /**
   * Copy constructor.
   *
   * @param other a CFAbstractStore to copy into this
   */
  protected CFAbstractStore(CFAbstractStore<V, S> other) {
    this.analysis = other.analysis;
    this.localVariableValues = new HashMap<>(other.localVariableValues);
    this.thisValue = other.thisValue;
    this.fieldValues = new HashMap<>(other.fieldValues);
    this.methodCallExpressions = new HashMap<>(other.methodCallExpressions);
    this.arrayValues = new HashMap<>(other.arrayValues);
    this.classValues = new HashMap<>(other.classValues);
    this.sequentialSemantics = other.sequentialSemantics;
    this.assumeSideEffectFree = other.assumeSideEffectFree;
    this.assumePureGetters = other.assumePureGetters;
  }

  /**
   * Set the abstract value of a method parameter (only adds the information to the store, does not
   * remove any other knowledge). Any previous information is erased; this method should only be
   * used to initialize the abstract value.
   */
  public void initializeMethodParameter(LocalVariableNode p, @Nullable V value) {
    if (value != null) {
      localVariableValues.put(new LocalVariable(p.getElement()), value);
    }
  }

  /**
   * Set the value of the current object. Any previous information is erased; this method should
   * only be used to initialize the value.
   */
  public void initializeThisValue(AnnotationMirror a, TypeMirror underlyingType) {
    if (a != null) {
      thisValue = analysis.createSingleAnnotationValue(a, underlyingType);
    }
  }

  /**
   * Indicates whether the given method is side-effect-free as far as the current store is
   * concerned. In some cases, a store for a checker allows for other mechanisms to specify whether
   * a method is side-effect-free. For example, unannotated methods may be considered
   * side-effect-free by default.
   *
   * @param atypeFactory the type factory used to retrieve annotations on the method element
   * @param method the method element
   * @return whether the method is side-effect-free
   * @deprecated use {@link org.checkerframework.javacutil.AnnotationProvider#isSideEffectFree}
   */
  @Deprecated // 2022-09-27
  protected boolean isSideEffectFree(AnnotatedTypeFactory atypeFactory, ExecutableElement method) {
    return atypeFactory.isSideEffectFree(method);
  }

  /* --------------------------------------------------------- */
  /* Handling of fields */
  /* --------------------------------------------------------- */

  /**
   * Remove any information that might not be valid any more after a method call, and add
   * information guaranteed by the method.
   *
   * <ol>
   *   <li>If the method is side-effect-free (as indicated by {@link
   *       org.checkerframework.dataflow.qual.SideEffectFree} or {@link
   *       org.checkerframework.dataflow.qual.Pure}), then no information needs to be removed.
   *   <li>Otherwise, all information about field accesses {@code a.f} needs to be removed, except
   *       if the method {@code n} cannot modify {@code a.f}. This unmodifiability property holds if
   *       {@code a} is a local variable or {@code this}, and {@code f} is final, or if {@code a.f}
   *       has a {@link MonotonicQualifier} in the current store. Subclasses can change this
   *       behavior by overriding {@link #newFieldValueAfterMethodCall(FieldAccess,
   *       GenericAnnotatedTypeFactory, CFAbstractValue)}.
   *   <li>Furthermore, if the field has a monotonic annotation, then its information can also be
   *       kept.
   * </ol>
   *
   * Furthermore, if the method is deterministic, we store its result {@code val} in the store.
   *
   * @param methodInvocationNode method whose information is being updated
   * @param atypeFactory the type factory of the associated checker
   * @param val abstract value of the method call
   */
  public void updateForMethodCall(
      MethodInvocationNode methodInvocationNode, AnnotatedTypeFactory atypeFactory, V val) {
    ExecutableElement method = methodInvocationNode.getTarget().getMethod();
    @SuppressWarnings("unchecked")
    GenericAnnotatedTypeFactory<V, S, ?, ?> gatypeFactory =
        (GenericAnnotatedTypeFactory<V, S, ?, ?>) atypeFactory;

    // Case 1: The method is side-effect-free.
    boolean hasSideEffect =
        !(assumeSideEffectFree
            || (assumePureGetters && ElementUtils.isGetter(method))
            || atypeFactory.isSideEffectFree(method));
    if (hasSideEffect) {

      boolean sideEffectsUnrefineAliases = gatypeFactory.sideEffectsUnrefineAliases;

      // update local variables
      // TODO: Also remove if any element/argument to the annotation is not
      // isUnmodifiableByOtherCode.  Example: @KeyFor("valueThatCanBeMutated").
      if (sideEffectsUnrefineAliases) {
        localVariableValues.entrySet().removeIf(e -> e.getKey().isModifiableByOtherCode());
      }

      // update this value
      if (sideEffectsUnrefineAliases) {
        thisValue = null;
      }

      // update field values
      if (sideEffectsUnrefineAliases) {
        fieldValues.entrySet().removeIf(e -> e.getKey().isModifiableByOtherCode());
      } else {
        // Case 2 (unassignable fields) and case 3 (monotonic fields)
        updateFieldValuesForMethodCall(gatypeFactory);
      }

      // update array values
      arrayValues.clear();

      // update method values
      methodCallExpressions.keySet().removeIf(MethodCall::isModifiableByOtherCode);
    }

    // store information about method call if possible
    JavaExpression methodCall = JavaExpression.fromNode(methodInvocationNode);
    replaceValue(methodCall, val);
  }

  /**
   * Returns the new value of a field after a method call, or {@code null} if the field should be
   * removed from the store.
   *
   * <p>In this default implementation, the field's value is preserved if it is either unassignable
   * (see {@link FieldAccess#isUnassignableByOtherCode()}) or has a monotonic qualifier (see {@link
   * #newMonotonicFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory,
   * CFAbstractValue)}). Otherwise, it is removed from the store.
   *
   * @param fieldAccess the field whose value to update
   * @param atypeFactory AnnotatedTypeFactory of the associated checker
   * @param value the field's value before the method call
   * @return the field's value after the method call, or {@code null} if the field should be removed
   *     from the store
   */
  protected V newFieldValueAfterMethodCall(
      FieldAccess fieldAccess, GenericAnnotatedTypeFactory<V, S, ?, ?> atypeFactory, V value) {
    // Handle unassignable fields.
    if (!fieldAccess.isAssignableByOtherCode()) {
      return value;
    }

    // Handle fields with monotonic annotations.
    return newMonotonicFieldValueAfterMethodCall(fieldAccess, atypeFactory, value);
  }

  /**
   * Computes the value of a field whose declaration has a monotonic annotation, or returns {@code
   * null} if the field has no monotonic annotation.
   *
   * <p>Used by {@link #newFieldValueAfterMethodCall(FieldAccess, GenericAnnotatedTypeFactory,
   * CFAbstractValue)} to handle fields with monotonic annotations.
   *
   * @param fieldAccess the field whose value to compute
   * @param atypeFactory AnnotatedTypeFactory of the associated checker
   * @param value the field's value before the method call
   * @return the field's value after the method call, or {@code null} if the field has no monotonic
   *     annotation
   */
  protected V newMonotonicFieldValueAfterMethodCall(
      FieldAccess fieldAccess, GenericAnnotatedTypeFactory<V, S, ?, ?> atypeFactory, V value) {

    // case 3: the field has a monotonic annotation
    if (atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) {
      return null;
    }

    List<IPair<AnnotationMirror, AnnotationMirror>> fieldAnnotationPairs =
        atypeFactory.getAnnotationWithMetaAnnotation(
            fieldAccess.getField(), MonotonicQualifier.class);
    List<AnnotationMirror> metaAnnotations =
        CollectionsPlume.withoutDuplicates(
            CollectionsPlume.mapList(pair -> pair.second, fieldAnnotationPairs));
    List<AnnotationMirror> monotonicAnnotations = new ArrayList<>(metaAnnotations.size());
    for (AnnotationMirror metaAnnotation : metaAnnotations) {
      @SuppressWarnings("deprecation") // permitted for use in the framework
      Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false);
      monotonicAnnotations.add(
          AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName));
    }
    Collection<AnnotationMirror> valueAnnos = value.getAnnotations();
    V newValue = null;
    for (AnnotationMirror monotonicAnnotation : monotonicAnnotations) {
      // Make sure the target annotation is present.
      if (AnnotationUtils.containsSame(valueAnnos, monotonicAnnotation)) {
        newValue =
            analysis
                .createSingleAnnotationValue(monotonicAnnotation, value.getUnderlyingType())
                .mostSpecific(newValue, null);
      }
    }
    return newValue;
  }

  /**
   * Helper for {@link #updateForMethodCall(MethodInvocationNode, AnnotatedTypeFactory,
   * CFAbstractValue)}. Remove any information about field values that might not be valid any more
   * after a method call, and add information guaranteed by the method.
   *
   * <p>More specifically, remove all information about fields except for unassignable fields and
   * fields that have a monotonic annotation.
   *
   * @param atypeFactory AnnotatedTypeFactory of the associated checker
   */
  private void updateFieldValuesForMethodCall(
      GenericAnnotatedTypeFactory<V, S, ?, ?> atypeFactory) {
    Map<FieldAccess, V> newFieldValues = new HashMap<>(CollectionsPlume.mapCapacity(fieldValues));
    for (Map.Entry<FieldAccess, V> e : fieldValues.entrySet()) {
      FieldAccess fieldAccess = e.getKey();
      V value = e.getValue();

      V newValue = newFieldValueAfterMethodCall(fieldAccess, atypeFactory, value);
      if (newValue != null) {
        // Keep information for all hierarchies where we had a monotonic annotation.
        newFieldValues.put(fieldAccess, newValue);
      }
    }
    fieldValues = newFieldValues;
  }

  /**
   * Add the annotation {@code a} for the expression {@code expr} (correctly deciding where to store
   * the information depending on the type of the expression {@code expr}).
   *
   * <p>This method does not take care of removing other information that might be influenced by
   * changes to certain parts of the state.
   *
   * <p>If there is already a value {@code v} present for {@code expr}, then the stronger of the new
   * and old value are taken (according to the lattice). Note that this happens per hierarchy, and
   * if the store already contains information about a hierarchy other than {@code a}s hierarchy,
   * that information is preserved.
   *
   * <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
   * store.
   *
   * @param expr an expression
   * @param a an annotation for the expression
   */
  public void insertValue(JavaExpression expr, AnnotationMirror a) {
    insertValue(expr, analysis.createSingleAnnotationValue(a, expr.getType()));
  }

  /**
   * Like {@link #insertValue(JavaExpression, AnnotationMirror)}, but permits nondeterministic
   * expressions to be stored.
   *
   * <p>For an explanation of when to permit nondeterministic expressions, see {@link
   * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}.
   *
   * @param expr an expression
   * @param a an annotation for the expression
   */
  public void insertValuePermitNondeterministic(JavaExpression expr, AnnotationMirror a) {
    insertValuePermitNondeterministic(
        expr, analysis.createSingleAnnotationValue(a, expr.getType()));
  }

  /**
   * Add the annotation {@code newAnno} for the expression {@code expr} (correctly deciding where to
   * store the information depending on the type of the expression {@code expr}).
   *
   * <p>This method does not take care of removing other information that might be influenced by
   * changes to certain parts of the state.
   *
   * <p>If there is already a value {@code v} present for {@code expr}, then the greatest lower
   * bound of the new and old value is inserted into the store.
   *
   * <p>Note that this happens per hierarchy, and if the store already contains information about a
   * hierarchy other than {@code newAnno}'s hierarchy, that information is preserved.
   *
   * <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
   * store.
   *
   * @param expr an expression
   * @param newAnno the expression's annotation
   */
  public final void insertOrRefine(JavaExpression expr, AnnotationMirror newAnno) {
    insertOrRefine(expr, newAnno, false);
  }

  /**
   * Like {@link #insertOrRefine(JavaExpression, AnnotationMirror)}, but permits nondeterministic
   * expressions to be inserted.
   *
   * <p>For an explanation of when to permit nondeterministic expressions, see {@link
   * #insertValuePermitNondeterministic(JavaExpression, CFAbstractValue)}.
   *
   * @param expr an expression
   * @param newAnno the expression's annotation
   */
  public final void insertOrRefinePermitNondeterministic(
      JavaExpression expr, AnnotationMirror newAnno) {
    insertOrRefine(expr, newAnno, true);
  }

  /**
   * Helper function for {@link #insertOrRefine(JavaExpression, AnnotationMirror)} and {@link
   * #insertOrRefinePermitNondeterministic}.
   *
   * @param expr an expression
   * @param newAnno the expression's annotation
   * @param permitNondeterministic whether nondeterministic expressions may be inserted into the
   *     store
   */
  protected void insertOrRefine(
      JavaExpression expr, AnnotationMirror newAnno, boolean permitNondeterministic) {
    if (!canInsertJavaExpression(expr)) {
      return;
    }
    if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) {
      return;
    }

    V newValue = analysis.createSingleAnnotationValue(newAnno, expr.getType());
    V oldValue = getValue(expr);
    if (oldValue == null) {
      insertValue(
          expr,
          analysis.createSingleAnnotationValue(newAnno, expr.getType()),
          permitNondeterministic);
      return;
    }
    computeNewValueAndInsert(
        expr, newValue, CFAbstractValue<V>::greatestLowerBound, permitNondeterministic);
  }

  /** Returns true if {@code expr} can be stored in this store. */
  public static boolean canInsertJavaExpression(JavaExpression expr) {
    if (expr instanceof FieldAccess
        || expr instanceof ThisReference
        || expr instanceof SuperReference
        || expr instanceof LocalVariable
        || expr instanceof MethodCall
        || expr instanceof ArrayAccess
        || expr instanceof ClassName) {
      return !expr.containsUnknown();
    }
    return false;
  }

  /**
   * Add the abstract value {@code value} for the expression {@code expr} (correctly deciding where
   * to store the information depending on the type of the expression {@code expr}).
   *
   * <p>This method does not take care of removing other information that might be influenced by
   * changes to certain parts of the state.
   *
   * <p>If there is already a value {@code v} present for {@code expr}, then the stronger of the new
   * and old value are taken (according to the lattice). Note that this happens per hierarchy, and
   * if the store already contains information about a hierarchy for which {@code value} does not
   * contain information, then that information is preserved.
   *
   * <p>If {@code expr} is nondeterministic, this method does not insert {@code value} into the
   * store.
   *
   * @param expr the expression to insert in the store
   * @param value the value of the expression
   */
  public final void insertValue(JavaExpression expr, @Nullable V value) {
    insertValue(expr, value, false);
  }

  /**
   * Like {@link #insertValue(JavaExpression, CFAbstractValue)}, but updates the store even if
   * {@code expr} is nondeterministic.
   *
   * <p>Usually, nondeterministic JavaExpressions should not be stored in a Store. For example, in
   * the body of {@code if (nondet() == 3) {...}}, the store should not record that the value of
   * {@code nondet()} is 3, because it might not be 3 the next time {@code nondet()} is executed.
   *
   * <p>However, contracts can mention a nondeterministic JavaExpression. For example, a contract
   * might have a postcondition that {@code nondet()} is odd. This means that the next call to
   * {@code nondet()} will return odd. Such a postcondition may be evicted from the store by calling
   * a side-effecting method.
   *
   * @param expr the expression to insert in the store
   * @param value the value of the expression
   */
  public final void insertValuePermitNondeterministic(JavaExpression expr, @Nullable V value) {
    insertValue(expr, value, true);
  }

  /**
   * Returns true if the given (expression, value) pair can be inserted in the store, namely if the
   * value is non-null and the expression does not contain unknown or a nondeterministic expression.
   *
   * <p>This method returning true does not guarantee that the value will be inserted; the
   * implementation of {@link #insertValue( JavaExpression, CFAbstractValue, boolean)} might still
   * not insert it.
   *
   * @param expr the expression to insert in the store
   * @param value the value of the expression
   * @param permitNondeterministic if false, returns false if {@code expr} is nondeterministic; if
   *     true, permits nondeterministic expressions to be placed in the store
   * @return true if the given (expression, value) pair can be inserted in the store
   */
  @EnsuresNonNullIf(expression = "#2", result = true)
  protected boolean shouldInsert(
      JavaExpression expr, @Nullable V value, boolean permitNondeterministic) {
    if (value == null) {
      // No need to insert a null abstract value because it represents
      // top and top is also the default value.
      return false;
    }
    if (expr.containsUnknown()) {
      // Expressions containing unknown expressions are not stored.
      return false;
    }
    if (!(permitNondeterministic || expr.isDeterministic(analysis.getTypeFactory()))) {
      // Nondeterministic expressions may not be stored.
      // (They are likely to be quickly evicted, as soon as a side-effecting method is
      // called.)
      return false;
    }
    return true;
  }

  /**
   * Helper method for {@link #insertValue(JavaExpression, CFAbstractValue)} and {@link
   * #insertValuePermitNondeterministic}.
   *
   * <p>Every overriding implementation should start with
   *
   * <pre>{@code
   * if (!shouldInsert) {
   *   return;
   * }
   * }</pre>
   *
   * @param expr the expression to insert in the store
   * @param value the value of the expression
   * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if
   *     true, permits nondeterministic expressions to be placed in the store
   */
  protected void insertValue(
      JavaExpression expr, @Nullable V value, boolean permitNondeterministic) {
    computeNewValueAndInsert(
        expr, value, (old, newValue) -> newValue.mostSpecific(old, null), permitNondeterministic);
  }

  /**
   * Inserts the result of applying {@code merger} to {@code value} and the previous value for
   * {@code expr}.
   *
   * @param expr the JavaExpression
   * @param value the value of the JavaExpression
   * @param merger the function used to merge {@code value} and the previous value of {@code expr}
   * @param permitNondeterministic if false, does nothing if {@code expr} is nondeterministic; if
   *     true, permits nondeterministic expressions to be placed in the store
   */
  protected void computeNewValueAndInsert(
      JavaExpression expr,
      @Nullable V value,
      BinaryOperator<V> merger,
      boolean permitNondeterministic) {
    if (!shouldInsert(expr, value, permitNondeterministic)) {
      return;
    }

    if (expr instanceof LocalVariable) {
      LocalVariable localVar = (LocalVariable) expr;
      V oldValue = localVariableValues.get(localVar);
      V newValue = merger.apply(oldValue, value);
      if (newValue != null) {
        localVariableValues.put(localVar, newValue);
      }
    } else if (expr instanceof FieldAccess) {
      FieldAccess fieldAcc = (FieldAccess) expr;
      // Only store information about final fields (where the receiver is
      // also fixed) if concurrent semantics are enabled.
      boolean isMonotonic = isMonotonicUpdate(fieldAcc, value);
      if (sequentialSemantics || isMonotonic || !fieldAcc.isAssignableByOtherCode()) {
        V oldValue = fieldValues.get(fieldAcc);
        V newValue = merger.apply(oldValue, value);
        if (newValue != null) {
          fieldValues.put(fieldAcc, newValue);
        }
      }
    } else if (expr instanceof MethodCall) {
      MethodCall method = (MethodCall) expr;
      // Don't store any information if concurrent semantics are enabled.
      if (sequentialSemantics) {
        V oldValue = methodCallExpressions.get(method);
        V newValue = merger.apply(oldValue, value);
        if (newValue != null) {
          methodCallExpressions.put(method, newValue);
        }
      }
    } else if (expr instanceof ArrayAccess) {
      ArrayAccess arrayAccess = (ArrayAccess) expr;
      if (sequentialSemantics) {
        V oldValue = arrayValues.get(arrayAccess);
        V newValue = merger.apply(oldValue, value);
        if (newValue != null) {
          arrayValues.put(arrayAccess, newValue);
        }
      }
    } else if (expr instanceof ThisReference || expr instanceof SuperReference) {
      if (sequentialSemantics || !expr.isAssignableByOtherCode()) {
        V oldValue = thisValue;
        V newValue = merger.apply(oldValue, value);
        if (newValue != null) {
          thisValue = newValue;
        }
      }
    } else if (expr instanceof ClassName) {
      ClassName className = (ClassName) expr;
      if (sequentialSemantics || !className.isAssignableByOtherCode()) {
        V oldValue = classValues.get(className);
        V newValue = merger.apply(oldValue, value);
        if (newValue != null) {
          classValues.put(className, newValue);
        }
      }
    } else {
      // No other types of expressions need to be stored.
    }
  }

  /**
   * Return true if fieldAcc is an update of a monotonic qualifier to its target qualifier.
   * (e.g. @MonotonicNonNull to @NonNull). Always returns false if {@code sequentialSemantics} is
   * true.
   *
   * @return true if fieldAcc is an update of a monotonic qualifier to its target qualifier
   *     (e.g. @MonotonicNonNull to @NonNull)
   */
  protected boolean isMonotonicUpdate(FieldAccess fieldAcc, V value) {
    if (analysis.atypeFactory.getSupportedMonotonicTypeQualifiers().isEmpty()) {
      return false;
    }
    boolean isMonotonic = false;
    // TODO: This check for !sequentialSemantics is an optimization that breaks the contract of
    // the method, since the method name and documentation say nothing about sequential
    // semantics.  This check should be performed by callers of this method when needed.
    // TODO: Update the javadoc of this method when the above to-do item is addressed.
    if (!sequentialSemantics) { // only compute if necessary
      AnnotatedTypeFactory atypeFactory = this.analysis.atypeFactory;
      List<IPair<AnnotationMirror, AnnotationMirror>> fieldAnnotations =
          atypeFactory.getAnnotationWithMetaAnnotation(
              fieldAcc.getField(), MonotonicQualifier.class);
      for (IPair<AnnotationMirror, AnnotationMirror> fieldAnnotation : fieldAnnotations) {
        AnnotationMirror metaAnnotation = fieldAnnotation.second;
        @SuppressWarnings("deprecation") // permitted for use in the framework
        Name annoName = AnnotationUtils.getElementValueClassName(metaAnnotation, "value", false);
        AnnotationMirror monotonicAnnotation =
            AnnotationBuilder.fromName(atypeFactory.getElementUtils(), annoName);
        // Make sure the 'target' annotation is present.
        if (AnnotationUtils.containsSame(value.getAnnotations(), monotonicAnnotation)) {
          isMonotonic = true;
          break;
        }
      }
    }
    return isMonotonic;
  }

  public void insertThisValue(AnnotationMirror a, TypeMirror underlyingType) {
    if (a == null) {
      return;
    }

    V value = analysis.createSingleAnnotationValue(a, underlyingType);

    V oldValue = thisValue;
    V newValue = value.mostSpecific(oldValue, null);
    if (newValue != null) {
      thisValue = newValue;
    }
  }

  /**
   * Completely replaces the abstract value {@code value} for the expression {@code expr} (correctly
   * deciding where to store the information depending on the type of the expression {@code expr}).
   * Any previous information is discarded.
   *
   * <p>This method does not take care of removing other information that might be influenced by
   * changes to certain parts of the state.
   */
  public void replaceValue(JavaExpression expr, @Nullable V value) {
    clearValue(expr);
    insertValue(expr, value);
  }

  /**
   * Remove any knowledge about the expression {@code expr} (correctly deciding where to remove the
   * information depending on the type of the expression {@code expr}).
   */
  public void clearValue(JavaExpression expr) {
    if (expr.containsUnknown()) {
      // Expressions containing unknown expressions are not stored.
      return;
    }
    if (expr instanceof LocalVariable) {
      LocalVariable localVar = (LocalVariable) expr;
      localVariableValues.remove(localVar);
    } else if (expr instanceof FieldAccess) {
      FieldAccess fieldAcc = (FieldAccess) expr;
      fieldValues.remove(fieldAcc);
    } else if (expr instanceof MethodCall) {
      MethodCall method = (MethodCall) expr;
      methodCallExpressions.remove(method);
    } else if (expr instanceof ArrayAccess) {
      ArrayAccess a = (ArrayAccess) expr;
      arrayValues.remove(a);
    } else if (expr instanceof ClassName) {
      ClassName c = (ClassName) expr;
      classValues.remove(c);
    } else if (expr instanceof ThisReference) {
      thisValue = null;
    } else {
      // No other types of expressions are stored.
    }
  }

  /**
   * Returns the current abstract value of a Java expression, or {@code null} if no information is
   * available.
   *
   * @return the current abstract value of a Java expression, or {@code null} if no information is
   *     available
   */
  public @Nullable V getValue(JavaExpression expr) {
    if (expr instanceof LocalVariable) {
      LocalVariable localVar = (LocalVariable) expr;
      return localVariableValues.get(localVar);
    } else if (expr instanceof ThisReference || expr instanceof SuperReference) {
      return thisValue;
    } else if (expr instanceof FieldAccess) {
      FieldAccess fieldAcc = (FieldAccess) expr;
      return fieldValues.get(fieldAcc);
    } else if (expr instanceof MethodCall) {
      MethodCall method = (MethodCall) expr;
      return methodCallExpressions.get(method);
    } else if (expr instanceof ArrayAccess) {
      ArrayAccess a = (ArrayAccess) expr;
      return arrayValues.get(a);
    } else if (expr instanceof ClassName) {
      ClassName c = (ClassName) expr;
      return classValues.get(c);
    } else {
      throw new BugInCF("Unexpected JavaExpression: " + expr + " (" + expr.getClass() + ")");
    }
  }

  /**
   * Returns the current abstract value of a field access, or {@code null} if no information is
   * available.
   *
   * @param n the node whose abstract value to return
   * @return the current abstract value of a field access, or {@code null} if no information is
   *     available
   */
  public @Nullable V getValue(FieldAccessNode n) {
    JavaExpression je = JavaExpression.fromNodeFieldAccess(n);
    if (je instanceof FieldAccess) {
      return fieldValues.get((FieldAccess) je);
    } else if (je instanceof ClassName) {
      return classValues.get((ClassName) je);
    } else if (je instanceof ThisReference || je instanceof SuperReference) {
      // "return thisValue" is wrong, because the node refers to an outer this.
      // So, return null for now.  TODO: improve.
      return null;
    } else {
      throw new BugInCF(
          "Unexpected JavaExpression %s %s for FieldAccessNode %s",
          je.getClass().getSimpleName(), je, n);
    }
  }

  /**
   * Returns the current abstract value of a field access, or {@code null} if no information is
   * available.
   *
   * @param fieldAccess the field access to look up in this store
   * @return current abstract value of a field access, or {@code null} if no information is
   *     available
   */
  public @Nullable V getFieldValue(FieldAccess fieldAccess) {
    return fieldValues.get(fieldAccess);
  }

  /**
   * Returns the current abstract value of a method call, or {@code null} if no information is
   * available.
   *
   * @param n a method call
   * @return the current abstract value of a method call, or {@code null} if no information is
   *     available
   */
  public @Nullable V getValue(MethodInvocationNode n) {
    JavaExpression method = JavaExpression.fromNode(n);
    if (method == null) {
      return null;
    }
    return methodCallExpressions.get(method);
  }

  /**
   * Returns the current abstract value of a field access, or {@code null} if no information is
   * available.
   *
   * @param n the node whose abstract value to return
   * @return the current abstract value of a field access, or {@code null} if no information is
   *     available
   */
  public @Nullable V getValue(ArrayAccessNode n) {
    ArrayAccess arrayAccess = JavaExpression.fromArrayAccess(n);
    return arrayValues.get(arrayAccess);
  }

  /**
   * Update the information in the store by considering an assignment with target {@code n}.
   *
   * @param n the left-hand side of an assignment
   * @param val the right-hand value of an assignment
   */
  public void updateForAssignment(Node n, @Nullable V val) {
    JavaExpression je = JavaExpression.fromNode(n);
    if (je instanceof ArrayAccess) {
      updateForArrayAssignment((ArrayAccess) je, val);
    } else if (je instanceof FieldAccess) {
      updateForFieldAccessAssignment((FieldAccess) je, val);
    } else if (je instanceof LocalVariable) {
      updateForLocalVariableAssignment((LocalVariable) je, val);
    } else {
      throw new BugInCF("Unexpected je of class " + je.getClass());
    }
  }

  /**
   * Update the information in the store by considering a field assignment with target {@code n},
   * where the right hand side has the abstract value {@code val}.
   *
   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
   *     abstract value is not known).
   */
  protected void updateForFieldAccessAssignment(FieldAccess fieldAccess, @Nullable V val) {
    removeConflicting(fieldAccess, val);
    if (!fieldAccess.containsUnknown() && val != null) {
      // Only store information about final fields (where the receiver is
      // also fixed) if concurrent semantics are enabled.
      if (sequentialSemantics
          || isMonotonicUpdate(fieldAccess, val)
          || !fieldAccess.isAssignableByOtherCode()) {
        fieldValues.put(fieldAccess, val);
      }
    }
  }

  /**
   * Update the information in the store by considering an assignment with target {@code n}, where
   * the target is an array access.
   *
   * <p>See {@link #removeConflicting(ArrayAccess,CFAbstractValue)}, as it is called first by this
   * method.
   */
  protected void updateForArrayAssignment(ArrayAccess arrayAccess, @Nullable V val) {
    removeConflicting(arrayAccess, val);
    if (!arrayAccess.containsUnknown() && val != null) {
      // Only store information about final fields (where the receiver is
      // also fixed) if concurrent semantics are enabled.
      if (sequentialSemantics) {
        arrayValues.put(arrayAccess, val);
      }
    }
  }

  /**
   * Set the abstract value of a local variable in the store. Overwrites any value that might have
   * been available previously.
   *
   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
   *     abstract value is not known).
   */
  protected void updateForLocalVariableAssignment(LocalVariable receiver, @Nullable V val) {
    removeConflicting(receiver);
    if (val != null) {
      localVariableValues.put(receiver, val);
    }
  }

  /**
   * Remove any information in this store that might not be true any more after {@code fieldAccess}
   * has been assigned a new value (with the abstract value {@code val}). This includes the
   * following steps (assume that {@code fieldAccess} is of the form <em>a.f</em> for some
   * <em>a</em>.
   *
   * <ol>
   *   <li value="1">Update the abstract value of other field accesses <em>b.g</em> where the field
   *       is equal (that is, <em>f=g</em>), and the receiver <em>b</em> might alias the receiver of
   *       {@code fieldAccess}, <em>a</em>. This update will raise the abstract value for such field
   *       accesses to at least {@code val} (or the old value, if that was less precise). However,
   *       this is only necessary if the field <em>g</em> is not final.
   *   <li value="2">Remove any abstract values for field accesses <em>b.g</em> where {@code
   *       fieldAccess} might alias any expression in the receiver <em>b</em>.
   *   <li value="3">Remove any information about method calls.
   *   <li value="4">Remove any abstract values an array access <em>b[i]</em> where {@code
   *       fieldAccess} might alias any expression in the receiver <em>a</em> or index <em>i</em>.
   * </ol>
   *
   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
   *     abstract value is not known).
   */
  protected void removeConflicting(FieldAccess fieldAccess, @Nullable V val) {
    Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator = fieldValues.entrySet().iterator();
    while (fieldValuesIterator.hasNext()) {
      Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
      FieldAccess otherFieldAccess = entry.getKey();
      V otherVal = entry.getValue();
      // case 2:
      if (otherFieldAccess.getReceiver().containsModifiableAliasOf(this, fieldAccess)) {
        fieldValuesIterator.remove(); // remove information completely
      }
      // case 1:
      else if (fieldAccess.getField().equals(otherFieldAccess.getField())) {
        if (canAlias(fieldAccess.getReceiver(), otherFieldAccess.getReceiver())) {
          if (!otherFieldAccess.isFinal()) {
            if (val != null) {
              V newVal = val.leastUpperBound(otherVal);
              entry.setValue(newVal);
            } else {
              // remove information completely
              fieldValuesIterator.remove();
            }
          }
        }
      }
    }

    Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator = arrayValues.entrySet().iterator();
    while (arrayValuesIterator.hasNext()) {
      Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
      ArrayAccess otherArrayAccess = entry.getKey();
      if (otherArrayAccess.containsModifiableAliasOf(this, fieldAccess)) {
        // remove information completely
        arrayValuesIterator.remove();
      }
    }

    // case 3:
    methodCallExpressions.clear();
  }

  /**
   * Remove any information in the store that might not be true any more after {@code arrayAccess}
   * has been assigned a new value (with the abstract value {@code val}). This includes the
   * following steps (assume that {@code arrayAccess} is of the form <em>a[i]</em> for some
   * <em>a</em>.
   *
   * <ol>
   *   <li value="1">Remove any abstract value for other array access <em>b[j]</em> where <em>a</em>
   *       and <em>b</em> can be aliases, or where either <em>b</em> or <em>j</em> contains a
   *       modifiable alias of <em>a[i]</em>.
   *   <li value="2">Remove any abstract values for field accesses <em>b.g</em> where <em>a[i]</em>
   *       might alias any expression in the receiver <em>b</em> and there is an array expression
   *       somewhere in the receiver.
   *   <li value="3">Remove any information about method calls.
   * </ol>
   *
   * @param val the abstract value of the value assigned to {@code n} (or {@code null} if the
   *     abstract value is not known).
   */
  protected void removeConflicting(ArrayAccess arrayAccess, @Nullable V val) {
    Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator = arrayValues.entrySet().iterator();
    while (arrayValuesIterator.hasNext()) {
      Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
      ArrayAccess otherArrayAccess = entry.getKey();
      // case 1:
      if (otherArrayAccess.containsModifiableAliasOf(this, arrayAccess)) {
        arrayValuesIterator.remove(); // remove information completely
      } else if (canAlias(arrayAccess.getArray(), otherArrayAccess.getArray())) {
        // TODO: one could be less strict here, and only raise the abstract
        // value for all array expressions with potentially aliasing receivers.
        arrayValuesIterator.remove(); // remove information completely
      }
    }

    // case 2:
    Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator = fieldValues.entrySet().iterator();
    while (fieldValuesIterator.hasNext()) {
      Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
      FieldAccess otherFieldAccess = entry.getKey();
      JavaExpression otherReceiver = otherFieldAccess.getReceiver();
      if (otherReceiver.containsModifiableAliasOf(this, arrayAccess)
          && otherReceiver.containsOfClass(ArrayAccess.class)) {
        // remove information completely
        fieldValuesIterator.remove();
      }
    }

    // case 3:
    methodCallExpressions.clear();
  }

  /**
   * Remove any information in this store that might not be true any more after {@code localVar} has
   * been assigned a new value. This includes the following steps:
   *
   * <ol>
   *   <li value="1">Remove any abstract values for field accesses <em>b.g</em> where {@code
   *       localVar} might alias any expression in the receiver <em>b</em>.
   *   <li value="2">Remove any abstract values for array accesses <em>a[i]</em> where {@code
   *       localVar} might alias the receiver <em>a</em>.
   *   <li value="3">Remove any information about method calls where the receiver or any of the
   *       parameters contains {@code localVar}.
   * </ol>
   */
  protected void removeConflicting(LocalVariable var) {
    Iterator<Map.Entry<FieldAccess, V>> fieldValuesIterator = fieldValues.entrySet().iterator();
    while (fieldValuesIterator.hasNext()) {
      Map.Entry<FieldAccess, V> entry = fieldValuesIterator.next();
      FieldAccess otherFieldAccess = entry.getKey();
      // case 1:
      if (otherFieldAccess.containsSyntacticEqualJavaExpression(var)) {
        fieldValuesIterator.remove();
      }
    }

    Iterator<Map.Entry<ArrayAccess, V>> arrayValuesIterator = arrayValues.entrySet().iterator();
    while (arrayValuesIterator.hasNext()) {
      Map.Entry<ArrayAccess, V> entry = arrayValuesIterator.next();
      ArrayAccess otherArrayAccess = entry.getKey();
      // case 2:
      if (otherArrayAccess.containsSyntacticEqualJavaExpression(var)) {
        arrayValuesIterator.remove();
      }
    }

    Iterator<Map.Entry<MethodCall, V>> methodCallValuesIterator =
        methodCallExpressions.entrySet().iterator();
    while (methodCallValuesIterator.hasNext()) {
      Map.Entry<MethodCall, V> entry = methodCallValuesIterator.next();
      MethodCall otherMethodAccess = entry.getKey();
      // case 3:
      if (otherMethodAccess.containsSyntacticEqualJavaExpression(var)) {
        methodCallValuesIterator.remove();
      }
    }
  }

  /**
   * Can the objects {@code a} and {@code b} be aliases? Returns a conservative answer (i.e.,
   * returns {@code true} if not enough information is available to determine aliasing).
   */
  @Override
  public boolean canAlias(JavaExpression a, JavaExpression b) {
    TypeMirror tb = b.getType();
    TypeMirror ta = a.getType();
    Types types = analysis.getTypes();
    return types.isSubtype(ta, tb) || types.isSubtype(tb, ta);
  }

  /* --------------------------------------------------------- */
  /* Handling of local variables */
  /* --------------------------------------------------------- */

  /**
   * Returns the current abstract value of a local variable, or {@code null} if no information is
   * available.
   *
   * @param n the local variable
   * @return the current abstract value of a local variable, or {@code null} if no information is
   *     available
   */
  public @Nullable V getValue(LocalVariableNode n) {
    VariableElement el = n.getElement();
    return localVariableValues.get(new LocalVariable(el));
  }

  /* --------------------------------------------------------- */
  /* Handling of the current object */
  /* --------------------------------------------------------- */

  /**
   * Returns the current abstract value of the current object, or {@code null} if no information is
   * available.
   *
   * @param n a reference to "this"
   * @return the current abstract value of the current object, or {@code null} if no information is
   *     available
   */
  public @Nullable V getValue(ThisNode n) {
    return thisValue;
  }

  /* --------------------------------------------------------- */
  /* Helper and miscellaneous methods */
  /* --------------------------------------------------------- */

  @SuppressWarnings("unchecked")
  @Override
  public S copy() {
    return analysis.createCopiedStore((S) this);
  }

  @Override
  public S leastUpperBound(S other) {
    return upperBound(other, false);
  }

  @Override
  public S widenedUpperBound(S previous) {
    return upperBound(previous, true);
  }

  private S upperBound(S other, boolean shouldWiden) {
    S newStore = analysis.createEmptyStore(sequentialSemantics);

    for (Map.Entry<LocalVariable, V> e : other.localVariableValues.entrySet()) {
      // local variables that are only part of one store, but not the other are discarded, as
      // one of store implicitly contains 'top' for that variable.
      LocalVariable localVar = e.getKey();
      V thisVal = localVariableValues.get(localVar);
      if (thisVal != null) {
        V otherVal = e.getValue();
        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);

        if (mergedVal != null) {
          newStore.localVariableValues.put(localVar, mergedVal);
        }
      }
    }

    // information about the current object
    {
      V otherVal = other.thisValue;
      V myVal = thisValue;
      if (myVal == null || otherVal == null) {
        newStore.thisValue = null;
      } else {
        newStore.thisValue = upperBoundOfValues(otherVal, myVal, shouldWiden);
      }
    }

    for (Map.Entry<FieldAccess, V> e : other.fieldValues.entrySet()) {
      // information about fields that are only part of one store, but not the other are
      // discarded, as one store implicitly contains 'top' for that field.
      FieldAccess el = e.getKey();
      V thisVal = fieldValues.get(el);
      if (thisVal != null) {
        V otherVal = e.getValue();
        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
        if (mergedVal != null) {
          newStore.fieldValues.put(el, mergedVal);
        }
      }
    }
    for (Map.Entry<ArrayAccess, V> e : other.arrayValues.entrySet()) {
      // information about arrays that are only part of one store, but not the other are
      // discarded, as one store implicitly contains 'top' for that array access.
      ArrayAccess el = e.getKey();
      V thisVal = arrayValues.get(el);
      if (thisVal != null) {
        V otherVal = e.getValue();
        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
        if (mergedVal != null) {
          newStore.arrayValues.put(el, mergedVal);
        }
      }
    }
    for (Map.Entry<MethodCall, V> e : other.methodCallExpressions.entrySet()) {
      // information about methods that are only part of one store, but not the other are
      // discarded, as one store implicitly contains 'top' for that field.
      MethodCall el = e.getKey();
      V thisVal = methodCallExpressions.get(el);
      if (thisVal != null) {
        V otherVal = e.getValue();
        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
        if (mergedVal != null) {
          newStore.methodCallExpressions.put(el, mergedVal);
        }
      }
    }
    for (Map.Entry<ClassName, V> e : other.classValues.entrySet()) {
      ClassName el = e.getKey();
      V thisVal = classValues.get(el);
      if (thisVal != null) {
        V otherVal = e.getValue();
        V mergedVal = upperBoundOfValues(otherVal, thisVal, shouldWiden);
        if (mergedVal != null) {
          newStore.classValues.put(el, mergedVal);
        }
      }
    }
    return newStore;
  }

  private V upperBoundOfValues(V otherVal, V thisVal, boolean shouldWiden) {
    return shouldWiden ? thisVal.widenUpperBound(otherVal) : thisVal.leastUpperBound(otherVal);
  }

  /**
   * Returns true iff this {@link CFAbstractStore} contains a superset of the map entries of the
   * argument {@link CFAbstractStore}. Note that we test the entry keys and values by Java equality,
   * not by any subtype relationship. This method is used primarily to simplify the equals
   * predicate.
   */
  protected boolean supersetOf(CFAbstractStore<V, S> other) {
    for (Map.Entry<LocalVariable, V> e : other.localVariableValues.entrySet()) {
      LocalVariable key = e.getKey();
      V value = localVariableValues.get(key);
      if (value == null || !value.equals(e.getValue())) {
        return false;
      }
    }
    if (!Objects.equals(thisValue, other.thisValue)) {
      return false;
    }
    for (Map.Entry<FieldAccess, V> e : other.fieldValues.entrySet()) {
      FieldAccess key = e.getKey();
      V value = fieldValues.get(key);
      if (value == null || !value.equals(e.getValue())) {
        return false;
      }
    }
    for (Map.Entry<ArrayAccess, V> e : other.arrayValues.entrySet()) {
      ArrayAccess key = e.getKey();
      V value = arrayValues.get(key);
      if (value == null || !value.equals(e.getValue())) {
        return false;
      }
    }
    for (Map.Entry<MethodCall, V> e : other.methodCallExpressions.entrySet()) {
      MethodCall key = e.getKey();
      V value = methodCallExpressions.get(key);
      if (value == null || !value.equals(e.getValue())) {
        return false;
      }
    }
    for (Map.Entry<ClassName, V> e : other.classValues.entrySet()) {
      ClassName key = e.getKey();
      V value = classValues.get(key);
      if (value == null || !value.equals(e.getValue())) {
        return false;
      }
    }
    return true;
  }

  @Override
  public boolean equals(@Nullable Object o) {
    if (o instanceof CFAbstractStore) {
      @SuppressWarnings("unchecked")
      CFAbstractStore<V, S> other = (CFAbstractStore<V, S>) o;
      return this.supersetOf(other) && other.supersetOf(this);
    } else {
      return false;
    }
  }

  @Override
  public int hashCode() {
    // What is a good hash code to use?
    return 22;
  }

  @SideEffectFree
  @Override
  public String toString() {
    return visualize(new StringCFGVisualizer<>());
  }

  @Override
  public String visualize(CFGVisualizer<?, S, ?> viz) {
    // This cast is guaranteed to be safe, as long as the CFGVisualizer is created by
    // CFGVisualizer<Value, Store, TransferFunction> createCFGVisualizer() of
    // GenericAnnotatedTypeFactory.
    @SuppressWarnings("unchecked")
    CFGVisualizer<V, S, ?> castedViz = (CFGVisualizer<V, S, ?>) viz;
    String internal = internalVisualize(castedViz);
    if (internal.trim().isEmpty()) {
      return this.getClassAndUid() + "()";
    } else {
      return this.getClassAndUid() + "(" + viz.getSeparator() + internal + ")";
    }
  }

  /**
   * Adds a representation of the internal information of this Store to visualizer {@code viz}.
   *
   * @param viz the visualizer
   * @return a representation of the internal information of this {@link Store}
   */
  protected String internalVisualize(CFGVisualizer<V, S, ?> viz) {
    StringJoiner res = new StringJoiner(viz.getSeparator());
    for (LocalVariable lv : ToStringComparator.sorted(localVariableValues.keySet())) {
      res.add(viz.visualizeStoreLocalVar(lv, localVariableValues.get(lv)));
    }
    if (thisValue != null) {
      res.add(viz.visualizeStoreThisVal(thisValue));
    }
    for (FieldAccess fa : ToStringComparator.sorted(fieldValues.keySet())) {
      res.add(viz.visualizeStoreFieldVal(fa, fieldValues.get(fa)));
    }
    for (ArrayAccess fa : ToStringComparator.sorted(arrayValues.keySet())) {
      res.add(viz.visualizeStoreArrayVal(fa, arrayValues.get(fa)));
    }
    for (MethodCall fa : ToStringComparator.sorted(methodCallExpressions.keySet())) {
      res.add(viz.visualizeStoreMethodVals(fa, methodCallExpressions.get(fa)));
    }
    for (ClassName fa : ToStringComparator.sorted(classValues.keySet())) {
      res.add(viz.visualizeStoreClassVals(fa, classValues.get(fa)));
    }
    return res.toString();
  }
}
