package org.checkerframework.checker.initialization;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.LiteralTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.checkerframework.checker.initialization.qual.FBCBottom;
import org.checkerframework.checker.initialization.qual.Initialized;
import org.checkerframework.checker.initialization.qual.NotOnlyInitialized;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.NullnessAnnotatedTypeFactory;
import org.checkerframework.checker.nullness.NullnessChecker;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.flow.CFAbstractValue;
import org.checkerframework.framework.qual.Unused;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedExecutableType;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.MostlyNoElementQualifierHierarchy;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.treeannotator.ListTreeAnnotator;
import org.checkerframework.framework.type.treeannotator.TreeAnnotator;
import org.checkerframework.framework.type.typeannotator.ListTypeAnnotator;
import org.checkerframework.framework.type.typeannotator.TypeAnnotator;
import org.checkerframework.framework.util.QualifierKind;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreePathUtil;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.IPair;

/**
 * The annotated type factory for the freedom-before-commitment type-system. The
 * freedom-before-commitment type-system and this class are abstract and need to be combined with
 * another type-system whose safe initialization should be tracked. For an example, see the {@link
 * NullnessChecker}.
 */
public abstract class InitializationAnnotatedTypeFactory<
        Value extends CFAbstractValue<Value>,
        Store extends InitializationStore<Value, Store>,
        Transfer extends InitializationTransfer<Value, Transfer, Store>,
        Flow extends CFAbstractAnalysis<Value, Store, Transfer>>
    extends GenericAnnotatedTypeFactory<Value, Store, Transfer, Flow> {

  /** {@link UnknownInitialization}. */
  protected final AnnotationMirror UNKNOWN_INITIALIZATION;

  /** {@link Initialized}. */
  protected final AnnotationMirror INITIALIZED;

  /** {@link UnderInitialization} or null. */
  protected final AnnotationMirror UNDER_INITALIZATION;

  /** {@link NotOnlyInitialized} or null. */
  protected final AnnotationMirror NOT_ONLY_INITIALIZED;

  /** {@link FBCBottom}. */
  protected final AnnotationMirror FBCBOTTOM;

  /** The java.lang.Object type. */
  protected final TypeMirror objectTypeMirror;

  /** The Unused.when field/element. */
  protected final ExecutableElement unusedWhenElement;

  /** The UnderInitialization.value field/element. */
  protected final ExecutableElement underInitializationValueElement;

  /** The UnknownInitialization.value field/element. */
  protected final ExecutableElement unknownInitializationValueElement;

  /** Cache for the initialization annotations. */
  protected final Set<Class<? extends Annotation>> initAnnos;

  /**
   * String representation of all initialization annotations.
   *
   * <p>{@link UnknownInitialization} {@link UnderInitialization} {@link Initialized} {@link
   * FBCBottom}
   *
   * <p>This is used to quickly check of an AnnotationMirror is one of the initialization
   * annotations without having to repeatedly convert them to strings.
   */
  protected final Set<String> initAnnoNames;

  /**
   * Create a new InitializationAnnotatedTypeFactory.
   *
   * @param checker the checker to which the new type factory belongs
   */
  protected InitializationAnnotatedTypeFactory(BaseTypeChecker checker) {
    super(checker, true);

    UNKNOWN_INITIALIZATION = AnnotationBuilder.fromClass(elements, UnknownInitialization.class);
    INITIALIZED = AnnotationBuilder.fromClass(elements, Initialized.class);
    UNDER_INITALIZATION = AnnotationBuilder.fromClass(elements, UnderInitialization.class);
    NOT_ONLY_INITIALIZED = AnnotationBuilder.fromClass(elements, NotOnlyInitialized.class);
    FBCBOTTOM = AnnotationBuilder.fromClass(elements, FBCBottom.class);

    objectTypeMirror = processingEnv.getElementUtils().getTypeElement("java.lang.Object").asType();
    unusedWhenElement = TreeUtils.getMethod(Unused.class, "when", 0, processingEnv);
    underInitializationValueElement =
        TreeUtils.getMethod(UnderInitialization.class, "value", 0, processingEnv);
    unknownInitializationValueElement =
        TreeUtils.getMethod(UnknownInitialization.class, "value", 0, processingEnv);

    Set<Class<? extends Annotation>> tempInitAnnos = new LinkedHashSet<>(4);
    tempInitAnnos.add(UnderInitialization.class);
    tempInitAnnos.add(Initialized.class);
    tempInitAnnos.add(UnknownInitialization.class);
    tempInitAnnos.add(FBCBottom.class);

    initAnnos = Collections.unmodifiableSet(tempInitAnnos);

    Set<String> tempInitAnnoNames = new HashSet<>(4);
    tempInitAnnoNames.add(AnnotationUtils.annotationName(UNKNOWN_INITIALIZATION));
    tempInitAnnoNames.add(AnnotationUtils.annotationName(UNDER_INITALIZATION));
    tempInitAnnoNames.add(AnnotationUtils.annotationName(INITIALIZED));
    tempInitAnnoNames.add(AnnotationUtils.annotationName(FBCBOTTOM));

    initAnnoNames = Collections.unmodifiableSet(tempInitAnnoNames);

    // No call to postInit() because this class is abstract.
    // Its subclasses must call postInit().
  }

  public Set<Class<? extends Annotation>> getInitializationAnnotations() {
    return initAnnos;
  }

  /**
   * Is the annotation {@code anno} an initialization qualifier?
   *
   * @param anno the annotation to check
   * @return true if the argument is an initialization qualifier
   */
  protected boolean isInitializationAnnotation(AnnotationMirror anno) {
    assert anno != null;
    return initAnnoNames.contains(AnnotationUtils.annotationName(anno));
  }

  /*
   * The following method can be used to appropriately configure the
   * commitment type-system.
   */

  /**
   * Returns the list of annotations that is forbidden for the constructor return type.
   *
   * @return the list of annotations that is forbidden for the constructor return type
   */
  public Set<Class<? extends Annotation>> getInvalidConstructorReturnTypeAnnotations() {
    return getInitializationAnnotations();
  }

  /**
   * Returns the annotation that makes up the invariant of this commitment type system, such as
   * {@code @NonNull}.
   *
   * @return the invariant annotation for this type system
   */
  public abstract AnnotationMirror getFieldInvariantAnnotation();

  /**
   * Returns whether or not {@code field} has the invariant annotation.
   *
   * <p>This method is a convenience method for {@link
   * #hasFieldInvariantAnnotation(AnnotatedTypeMirror, VariableElement)}.
   *
   * <p>If the {@code field} is a type variable, this method returns true if any possible
   * instantiation of the type parameter could have the invariant annotation. See {@link
   * NullnessAnnotatedTypeFactory#hasFieldInvariantAnnotation(VariableTree)} for an example.
   *
   * @param field field that might have invariant annotation
   * @return whether or not field has the invariant annotation
   */
  protected final boolean hasFieldInvariantAnnotation(VariableTree field) {
    AnnotatedTypeMirror type = getAnnotatedType(field);
    VariableElement fieldElement = TreeUtils.elementFromDeclaration(field);
    return hasFieldInvariantAnnotation(type, fieldElement);
  }

  /**
   * Returns whether or not {@code type} has the invariant annotation.
   *
   * <p>If the {@code type} is a type variable, this method returns true if any possible
   * instantiation of the type parameter could have the invariant annotation. See {@link
   * NullnessAnnotatedTypeFactory#hasFieldInvariantAnnotation(VariableTree)} for an example.
   *
   * @param type of field that might have invariant annotation
   * @param fieldElement the field element, which can be used to check annotations on the
   *     declaration
   * @return whether or not the type has the invariant annotation
   */
  protected abstract boolean hasFieldInvariantAnnotation(
      AnnotatedTypeMirror type, VariableElement fieldElement);

  /**
   * Creates a {@link UnderInitialization} annotation with the given type as its type frame
   * argument.
   *
   * @param typeFrame the type down to which some value has been initialized
   * @return an {@link UnderInitialization} annotation with the given argument
   */
  public AnnotationMirror createUnderInitializationAnnotation(TypeMirror typeFrame) {
    assert typeFrame != null;
    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
    builder.setValue("value", typeFrame);
    return builder.build();
  }

  /**
   * Creates a {@link UnderInitialization} annotation with the given type frame.
   *
   * @param typeFrame the type down to which some value has been initialized
   * @return an {@link UnderInitialization} annotation with the given argument
   */
  public AnnotationMirror createUnderInitializationAnnotation(Class<?> typeFrame) {
    assert typeFrame != null;
    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnderInitialization.class);
    builder.setValue("value", typeFrame);
    return builder.build();
  }

  /**
   * Creates a {@link UnknownInitialization} annotation with a given type frame.
   *
   * @param typeFrame the type down to which some value has been initialized
   * @return an {@link UnknownInitialization} annotation with the given argument
   */
  public AnnotationMirror createUnknownInitializationAnnotation(Class<?> typeFrame) {
    assert typeFrame != null;
    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class);
    builder.setValue("value", typeFrame);
    return builder.build();
  }

  /**
   * Creates an {@link UnknownInitialization} annotation with a given type frame.
   *
   * @param typeFrame the type down to which some value has been initialized
   * @return an {@link UnknownInitialization} annotation with the given argument
   */
  public AnnotationMirror createUnknownInitializationAnnotation(TypeMirror typeFrame) {
    assert typeFrame != null;
    AnnotationBuilder builder = new AnnotationBuilder(processingEnv, UnknownInitialization.class);
    builder.setValue("value", typeFrame);
    return builder.build();
  }

  /**
   * Returns the type frame (that is, the argument) of a given initialization annotation.
   *
   * @param annotation a {@link UnderInitialization} or {@link UnknownInitialization} annotation
   * @return the annotation's argument
   */
  public TypeMirror getTypeFrameFromAnnotation(AnnotationMirror annotation) {
    if (AnnotationUtils.areSameByName(
        annotation, "org.checkerframework.checker.initialization.qual.UnderInitialization")) {
      return AnnotationUtils.getElementValue(
          annotation, underInitializationValueElement, TypeMirror.class, objectTypeMirror);
    } else {
      return AnnotationUtils.getElementValue(
          annotation, unknownInitializationValueElement, TypeMirror.class, objectTypeMirror);
    }
  }

  /**
   * Is {@code anno} the {@link UnderInitialization} annotation (with any type frame)?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} is {@link UnderInitialization}
   */
  public boolean isUnderInitialization(AnnotationMirror anno) {
    return areSameByClass(anno, UnderInitialization.class);
  }

  /**
   * Is {@code anno} the {@link UnknownInitialization} annotation (with any type frame)?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} is {@link UnknownInitialization}
   */
  public boolean isUnknownInitialization(AnnotationMirror anno) {
    return areSameByClass(anno, UnknownInitialization.class);
  }

  /**
   * Is {@code anno} the bottom annotation?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} is {@link FBCBottom}
   */
  public boolean isFbcBottom(AnnotationMirror anno) {
    return AnnotationUtils.areSame(anno, FBCBOTTOM);
  }

  /**
   * Is {@code anno} the {@link Initialized} annotation?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} is {@link Initialized}
   */
  public boolean isInitialized(AnnotationMirror anno) {
    return AnnotationUtils.areSame(anno, INITIALIZED);
  }

  /**
   * Does {@code anno} have the annotation {@link UnderInitialization} (with any type frame)?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} has {@link UnderInitialization}
   */
  public boolean isUnderInitialization(AnnotatedTypeMirror anno) {
    return anno.hasEffectiveAnnotation(UnderInitialization.class);
  }

  /**
   * Does {@code anno} have the annotation {@link UnknownInitialization} (with any type frame)?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} has {@link UnknownInitialization}
   */
  public boolean isUnknownInitialization(AnnotatedTypeMirror anno) {
    return anno.hasEffectiveAnnotation(UnknownInitialization.class);
  }

  /**
   * Does {@code anno} have the bottom annotation?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} has {@link FBCBottom}
   */
  public boolean isFbcBottom(AnnotatedTypeMirror anno) {
    return anno.hasEffectiveAnnotation(FBCBottom.class);
  }

  /**
   * Does {@code anno} have the annotation {@link Initialized}?
   *
   * @param anno the annotation to check
   * @return true if {@code anno} has {@link Initialized}
   */
  public boolean isInitialized(AnnotatedTypeMirror anno) {
    return anno.hasEffectiveAnnotation(Initialized.class);
  }

  /**
   * Are all fields initialized-only?
   *
   * @param classTree the class to query
   * @return true if all fields are initialized-only
   */
  protected boolean areAllFieldsInitializedOnly(ClassTree classTree) {
    for (Tree member : classTree.getMembers()) {
      if (member.getKind() != Tree.Kind.VARIABLE) {
        continue;
      }
      VariableTree var = (VariableTree) member;
      VariableElement varElt = TreeUtils.elementFromDeclaration(var);
      // var is not initialized-only
      if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) {
        // var is not static -- need a check of initializer blocks,
        // not of constructor which is where this is used
        if (!varElt.getModifiers().contains(Modifier.STATIC)) {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * {@inheritDoc}
   *
   * <p>In most cases, subclasses want to call this method first because it may clear all
   * annotations and use the hierarchy's root annotations.
   */
  @Override
  public void postAsMemberOf(AnnotatedTypeMirror type, AnnotatedTypeMirror owner, Element element) {
    super.postAsMemberOf(type, owner, element);

    if (element.getKind().isField()) {
      Collection<? extends AnnotationMirror> declaredFieldAnnotations = getDeclAnnotations(element);
      AnnotatedTypeMirror fieldAnnotations = getAnnotatedType(element);
      computeFieldAccessType(type, declaredFieldAnnotations, owner, fieldAnnotations, element);
    }
  }

  /**
   * Controls which hierarchies' qualifiers are changed based on the receiver type and the declared
   * annotations for a field.
   *
   * @see #computeFieldAccessType
   * @see #getAnnotatedTypeLhs(Tree)
   */
  private boolean computingAnnotatedTypeMirrorOfLHS = false;

  @Override
  public AnnotatedTypeMirror getAnnotatedTypeLhs(Tree lhsTree) {
    boolean oldComputingAnnotatedTypeMirrorOfLHS = computingAnnotatedTypeMirrorOfLHS;
    computingAnnotatedTypeMirrorOfLHS = true;
    AnnotatedTypeMirror result = super.getAnnotatedTypeLhs(lhsTree);
    computingAnnotatedTypeMirrorOfLHS = oldComputingAnnotatedTypeMirrorOfLHS;
    return result;
  }

  @Override
  public @Nullable AnnotatedDeclaredType getSelfType(Tree tree) {
    AnnotatedDeclaredType selfType = super.getSelfType(tree);

    TreePath path = getPath(tree);
    AnnotatedDeclaredType enclosing = selfType;
    while (path != null && enclosing != null) {
      TreePath topLevelMemberPath = findTopLevelClassMemberForTree(path);
      if (topLevelMemberPath != null && topLevelMemberPath.getLeaf() != null) {
        Tree topLevelMember = topLevelMemberPath.getLeaf();
        if (topLevelMember.getKind() != Tree.Kind.METHOD
            || TreeUtils.isConstructor((MethodTree) topLevelMember)) {
          setSelfTypeInInitializationCode(tree, enclosing, topLevelMemberPath);
        }
        path = topLevelMemberPath.getParentPath();
        enclosing = enclosing.getEnclosingType();
      } else {
        break;
      }
    }

    return selfType;
  }

  /**
   * In the first enclosing class, find the path to the top-level member that contains {@code path}.
   *
   * @param path the path whose leaf is the target
   * @return path to a top-level member containing the leaf of {@code path}
   */
  @SuppressWarnings("interning:not.interned") // AST node comparison
  private @Nullable TreePath findTopLevelClassMemberForTree(TreePath path) {
    if (TreeUtils.isClassTree(path.getLeaf())) {
      path = path.getParentPath();
      if (path == null) {
        return null;
      }
    }
    ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
    if (enclosingClass != null) {
      List<? extends Tree> classMembers = enclosingClass.getMembers();
      TreePath searchPath = path;
      while (searchPath.getParentPath() != null
          && searchPath.getParentPath().getLeaf() != enclosingClass) {
        searchPath = searchPath.getParentPath();
        if (classMembers.contains(searchPath.getLeaf())) {
          return searchPath;
        }
      }
    }
    return null;
  }

  /**
   * Side-effects argument {@code selfType} to make it @Initialized or @UnderInitialization,
   * depending on whether all fields have been set.
   *
   * @param tree a tree
   * @param selfType the type to side-effect
   * @param path a path
   */
  protected void setSelfTypeInInitializationCode(
      Tree tree, AnnotatedDeclaredType selfType, TreePath path) {
    ClassTree enclosingClass = TreePathUtil.enclosingClass(path);
    Type classType = ((JCTree) enclosingClass).type;
    AnnotationMirror annotation = null;

    // If all fields are initialized-only, and they are all initialized,
    // then:
    //  - if the class is final, this is @Initialized
    //  - otherwise, this is @UnderInitialization(CurrentClass) as
    //    there might still be subclasses that need initialization.
    if (areAllFieldsInitializedOnly(enclosingClass)) {
      Store store = getStoreBefore(tree);
      if (store != null
          && getUninitializedInvariantFields(store, path, false, Collections.emptyList())
              .isEmpty()) {
        if (classType.isFinal()) {
          annotation = INITIALIZED;
        } else {
          annotation = createUnderInitializationAnnotation(classType);
        }
      }
    }

    if (annotation == null) {
      annotation = getUnderInitializationAnnotationOfSuperType(classType);
    }
    selfType.replaceAnnotation(annotation);
  }

  /**
   * Returns an {@link UnderInitialization} annotation that has the superclass of {@code type} as
   * type frame.
   *
   * @param type a type
   * @return true an {@link UnderInitialization} for the supertype of {@code type}
   */
  protected AnnotationMirror getUnderInitializationAnnotationOfSuperType(TypeMirror type) {
    // Find supertype if possible.
    AnnotationMirror annotation;
    List<? extends TypeMirror> superTypes = types.directSupertypes(type);
    TypeMirror superClass = null;
    for (TypeMirror superType : superTypes) {
      ElementKind kind = types.asElement(superType).getKind();
      if (kind == ElementKind.CLASS) {
        superClass = superType;
        break;
      }
    }
    // Create annotation.
    if (superClass != null) {
      annotation = createUnderInitializationAnnotation(superClass);
    } else {
      // Use Object as a valid super-class.
      annotation = createUnderInitializationAnnotation(Object.class);
    }
    return annotation;
  }

  /**
   * Returns the fields that are not yet initialized in a given store. The result is a pair of
   * lists:
   *
   * <ul>
   *   <li>fields that are not yet initialized and have the invariant annotation
   *   <li>fields that are not yet initialized and do not have the invariant annotation
   * </ul>
   *
   * @param store a store
   * @param path the current path, used to determine the current class
   * @param isStatic whether to report static fields or instance fields
   * @param receiverAnnotations the annotations on the receiver
   * @return the fields that are not yet initialized in a given store (a pair of lists)
   */
  public IPair<List<VariableTree>, List<VariableTree>> getUninitializedFields(
      Store store,
      TreePath path,
      boolean isStatic,
      Collection<? extends AnnotationMirror> receiverAnnotations) {
    ClassTree currentClass = TreePathUtil.enclosingClass(path);
    List<VariableTree> fields = TreeUtils.fieldsFromClassTree(currentClass);
    List<VariableTree> uninitWithInvariantAnno = new ArrayList<>();
    List<VariableTree> uninitWithoutInvariantAnno = new ArrayList<>();
    for (VariableTree field : fields) {
      if (isUnused(field, receiverAnnotations)) {
        continue; // don't consider unused fields
      }
      VariableElement fieldElem = TreeUtils.elementFromDeclaration(field);
      if (ElementUtils.isStatic(fieldElem) == isStatic) {
        // Has the field been initialized?
        if (!store.isFieldInitialized(fieldElem)) {
          // Does this field need to satisfy the invariant?
          if (hasFieldInvariantAnnotation(field)) {
            uninitWithInvariantAnno.add(field);
          } else {
            uninitWithoutInvariantAnno.add(field);
          }
        }
      }
    }
    return IPair.of(uninitWithInvariantAnno, uninitWithoutInvariantAnno);
  }

  /**
   * Returns the fields that have the invariant annotation and are not yet initialized in a given
   * store.
   *
   * @param store a store
   * @param path the current path, used to determine the current class
   * @param isStatic whether to report static fields or instance fields
   * @param receiverAnnotations the annotations on the receiver
   * @return the fields that have the invariant annotation and are not yet initialized in a given
   *     store (a pair of lists)
   */
  public final List<VariableTree> getUninitializedInvariantFields(
      Store store,
      TreePath path,
      boolean isStatic,
      List<? extends AnnotationMirror> receiverAnnotations) {
    return getUninitializedFields(store, path, isStatic, receiverAnnotations).first;
  }

  /**
   * Returns the fields that have the invariant annotation and are initialized in a given store.
   *
   * @param store a store
   * @param path the current path; used to compute the current class
   * @return the fields that have the invariant annotation and are initialized in a given store
   */
  public List<VariableTree> getInitializedInvariantFields(Store store, TreePath path) {
    ClassTree currentClass = TreePathUtil.enclosingClass(path);
    List<VariableTree> fields = TreeUtils.fieldsFromClassTree(currentClass);
    List<VariableTree> initializedFields = new ArrayList<>();
    for (VariableTree field : fields) {
      VariableElement fieldElem = TreeUtils.elementFromDeclaration(field);
      if (!ElementUtils.isStatic(fieldElem)) {
        // Does this field need to satisfy the invariant?
        if (hasFieldInvariantAnnotation(field)) {
          // Has the field been initialized?
          if (store.isFieldInitialized(fieldElem)) {
            initializedFields.add(field);
          }
        }
      }
    }
    return initializedFields;
  }

  /** Returns whether the field {@code f} is unused, given the annotations on the receiver. */
  private boolean isUnused(
      VariableTree field, Collection<? extends AnnotationMirror> receiverAnnos) {
    if (receiverAnnos.isEmpty()) {
      return false;
    }

    AnnotationMirror unused =
        getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class);
    if (unused == null) {
      return false;
    }

    Name when = AnnotationUtils.getElementValueClassName(unused, unusedWhenElement);
    for (AnnotationMirror anno : receiverAnnos) {
      Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName();
      if (annoName.contentEquals(when)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Return true if the type is initialized with respect to the given frame -- that is, all of the
   * fields of the frame are initialized.
   *
   * @param type the type whose initialization type qualifiers to check
   * @param frame a class in {@code type}'s class hierarchy
   * @return true if the type is initialized for the given frame
   */
  public boolean isInitializedForFrame(AnnotatedTypeMirror type, TypeMirror frame) {
    AnnotationMirror initializationAnno =
        type.getEffectiveAnnotationInHierarchy(UNKNOWN_INITIALIZATION);
    TypeMirror typeFrame = getTypeFrameFromAnnotation(initializationAnno);
    Types types = processingEnv.getTypeUtils();
    return types.isSubtype(typeFrame, types.erasure(frame));
  }

  /**
   * Determine the type of a field access (implicit or explicit) based on the receiver type and the
   * declared annotations for the field.
   *
   * @param type type of the field access expression
   * @param declaredFieldAnnotations annotations on the element
   * @param receiverType inferred annotations of the receiver
   */
  private void computeFieldAccessType(
      AnnotatedTypeMirror type,
      Collection<? extends AnnotationMirror> declaredFieldAnnotations,
      AnnotatedTypeMirror receiverType,
      AnnotatedTypeMirror fieldAnnotations,
      Element element) {
    // not necessary for primitive fields
    if (TypesUtils.isPrimitive(type.getUnderlyingType())) {
      return;
    }
    // not necessary if there is an explicit UnknownInitialization
    // annotation on the field
    if (AnnotationUtils.containsSameByName(
        fieldAnnotations.getPrimaryAnnotations(), UNKNOWN_INITIALIZATION)) {
      return;
    }
    if (isUnknownInitialization(receiverType) || isUnderInitialization(receiverType)) {

      TypeMirror fieldDeclarationType = element.getEnclosingElement().asType();
      boolean isInitializedForFrame = isInitializedForFrame(receiverType, fieldDeclarationType);
      if (isInitializedForFrame) {
        // The receiver is initialized for this frame.
        // Change the type of the field to @UnknownInitialization so that
        // anything can be assigned to this field.
        type.replaceAnnotation(UNKNOWN_INITIALIZATION);
      } else if (computingAnnotatedTypeMirrorOfLHS) {
        // The receiver is not initialized for this frame, but the type of a lhs is being
        // computed.
        // Change the type of the field to @UnknownInitialization so that
        // anything can be assigned to this field.
        type.replaceAnnotation(UNKNOWN_INITIALIZATION);
      } else {
        // The receiver is not initialized for this frame and the type being computed is not
        // a LHS.
        // Replace all annotations with the top annotation for that hierarchy.
        type.clearPrimaryAnnotations();
        type.addAnnotations(qualHierarchy.getTopAnnotations());
      }

      if (!AnnotationUtils.containsSame(declaredFieldAnnotations, NOT_ONLY_INITIALIZED)) {
        // add root annotation for all other hierarchies, and
        // Initialized for the initialization hierarchy
        type.replaceAnnotation(INITIALIZED);
      }
    }
  }

  @Override
  protected TypeAnnotator createTypeAnnotator() {
    return new ListTypeAnnotator(super.createTypeAnnotator(), new CommitmentTypeAnnotator(this));
  }

  @Override
  protected TreeAnnotator createTreeAnnotator() {
    return new ListTreeAnnotator(super.createTreeAnnotator(), new CommitmentTreeAnnotator(this));
  }

  protected class CommitmentTypeAnnotator extends TypeAnnotator {
    public CommitmentTypeAnnotator(InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
      super(atypeFactory);
    }

    @Override
    public Void visitExecutable(AnnotatedExecutableType t, Void p) {
      Void result = super.visitExecutable(t, p);
      Element elem = t.getElement();
      if (elem.getKind() == ElementKind.CONSTRUCTOR) {
        AnnotatedDeclaredType returnType = (AnnotatedDeclaredType) t.getReturnType();
        DeclaredType underlyingType = returnType.getUnderlyingType();
        returnType.replaceAnnotation(getUnderInitializationAnnotationOfSuperType(underlyingType));
      }
      return result;
    }
  }

  protected class CommitmentTreeAnnotator extends TreeAnnotator {

    public CommitmentTreeAnnotator(InitializationAnnotatedTypeFactory<?, ?, ?, ?> atypeFactory) {
      super(atypeFactory);
    }

    @Override
    public Void visitMethod(MethodTree tree, AnnotatedTypeMirror p) {
      Void result = super.visitMethod(tree, p);
      if (TreeUtils.isConstructor(tree)) {
        assert p instanceof AnnotatedExecutableType;
        AnnotatedExecutableType exeType = (AnnotatedExecutableType) p;
        DeclaredType underlyingType = (DeclaredType) exeType.getReturnType().getUnderlyingType();
        AnnotationMirror a = getUnderInitializationAnnotationOfSuperType(underlyingType);
        exeType.getReturnType().replaceAnnotation(a);
      }
      return result;
    }

    @Override
    public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror p) {
      super.visitNewClass(tree, p);
      boolean allInitialized = true;
      Type type = ((JCTree) tree).type;
      for (ExpressionTree a : tree.getArguments()) {
        if (!TreeUtils.isStandaloneExpression(a)) {
          continue;
        }
        boolean oldShouldCache = shouldCache;
        shouldCache = false;
        AnnotatedTypeMirror t = getAnnotatedType(a);
        shouldCache = oldShouldCache;
        allInitialized &= (isInitialized(t) || isFbcBottom(t));
      }
      if (!allInitialized) {
        p.replaceAnnotation(createUnderInitializationAnnotation(type));
        return null;
      }
      p.replaceAnnotation(INITIALIZED);
      return null;
    }

    @Override
    public Void visitLiteral(LiteralTree tree, AnnotatedTypeMirror type) {
      if (tree.getKind() != Tree.Kind.NULL_LITERAL) {
        type.addAnnotation(INITIALIZED);
      }
      return super.visitLiteral(tree, type);
    }

    @Override
    public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror annotatedTypeMirror) {
      if (TreeUtils.isArrayLengthAccess(tree)) {
        annotatedTypeMirror.replaceAnnotation(INITIALIZED);
      }
      return super.visitMemberSelect(tree, annotatedTypeMirror);
    }
  }

  /**
   * The {@link QualifierHierarchy} for the initialization type system.
   *
   * <p>Type systems extending the Initialization Checker should call methods {@link
   * InitializationQualifierHierarchy#isSubtypeInitialization} and {@link
   * InitializationQualifierHierarchy#leastUpperBoundInitialization} for appropriate qualifiers. See
   * protected subclass NullnessQualifierHierarchy within class {@link NullnessChecker} for an
   * example.
   */
  protected abstract class InitializationQualifierHierarchy
      extends MostlyNoElementQualifierHierarchy {

    /** Qualifier kind for the @{@link UnknownInitialization} annotation. */
    private final QualifierKind UNKNOWN_INIT;

    /** Qualifier kind for the @{@link UnderInitialization} annotation. */
    private final QualifierKind UNDER_INIT;

    /** Create an InitializationQualifierHierarchy. */
    protected InitializationQualifierHierarchy() {
      super(
          InitializationAnnotatedTypeFactory.this.getSupportedTypeQualifiers(),
          elements,
          InitializationAnnotatedTypeFactory.this);
      UNKNOWN_INIT = getQualifierKind(UNKNOWN_INITIALIZATION);
      UNDER_INIT = getQualifierKind(UNDER_INITALIZATION);
    }

    /**
     * Subtype testing for initialization annotations. Will return false if either qualifier is not
     * an initialization annotation. Subclasses should override isSubtype and call this method for
     * initialization qualifiers.
     *
     * @param subAnno subtype annotation
     * @param subKind subtype kind
     * @param superAnno supertype annotation
     * @param superKind supertype kind
     * @return true if subAnno is a subtype of superAnno in the initialization hierarchy
     */
    public boolean isSubtypeInitialization(
        AnnotationMirror subAnno,
        QualifierKind subKind,
        AnnotationMirror superAnno,
        QualifierKind superKind) {
      if (!subKind.isSubtypeOf(superKind)) {
        return false;
      } else if ((subKind == UNDER_INIT && superKind == UNDER_INIT)
          || (subKind == UNDER_INIT && superKind == UNKNOWN_INIT)
          || (subKind == UNKNOWN_INIT && superKind == UNKNOWN_INIT)) {
        // Thus, we only need to look at the type frame.
        TypeMirror frame1 = getTypeFrameFromAnnotation(subAnno);
        TypeMirror frame2 = getTypeFrameFromAnnotation(superAnno);
        return types.isSubtype(frame1, frame2);
      } else {
        return true;
      }
    }

    /**
     * Compute the least upper bound of two initialization qualifiers. Returns null if one of the
     * qualifiers is not in the initialization hierarachy. Subclasses should override
     * leastUpperBound and call this method for initialization qualifiers.
     *
     * @param anno1 an initialization qualifier
     * @param qual1 a qualifier kind
     * @param anno2 an initialization qualifier
     * @param qual2 a qualifier kind
     * @return the lub of anno1 and anno2
     */
    protected AnnotationMirror leastUpperBoundInitialization(
        AnnotationMirror anno1, QualifierKind qual1, AnnotationMirror anno2, QualifierKind qual2) {
      assert isInitializationAnnotation(anno1);
      assert isInitializationAnnotation(anno2);

      // Handle the case where one is a subtype of the other.
      if (isSubtypeInitialization(anno1, qual1, anno2, qual2)) {
        return anno2;
      } else if (isSubtypeInitialization(anno2, qual2, anno1, qual1)) {
        return anno1;
      }
      boolean unknowninit1 = isUnknownInitialization(anno1);
      boolean unknowninit2 = isUnknownInitialization(anno2);
      boolean underinit1 = isUnderInitialization(anno1);
      boolean underinit2 = isUnderInitialization(anno2);

      // Handle @Initialized.
      if (isInitialized(anno1)) {
        assert underinit2;
        return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno2));
      } else if (isInitialized(anno2)) {
        assert underinit1;
        return createUnknownInitializationAnnotation(getTypeFrameFromAnnotation(anno1));
      }

      if (underinit1 && underinit2) {
        return createUnderInitializationAnnotation(
            lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2)));
      }

      assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2);
      return createUnknownInitializationAnnotation(
          lubTypeFrame(getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2)));
    }

    /**
     * Returns the least upper bound of two Java basetypes (without annotations).
     *
     * @param a the first argument
     * @param b the second argument
     * @return the lub of the two arguments
     */
    protected TypeMirror lubTypeFrame(TypeMirror a, TypeMirror b) {
      if (types.isSubtype(a, b)) {
        return b;
      } else if (types.isSubtype(b, a)) {
        return a;
      }

      return TypesUtils.leastUpperBound(a, b, processingEnv);
    }

    /**
     * Compute the greatest lower bound of two initialization qualifiers. Returns null if one of the
     * qualifiers is not in the initialization hierarachy. Subclasses should override
     * greatestLowerBound and call this method for initialization qualifiers.
     *
     * @param anno1 an initialization qualifier
     * @param qual1 a qualifier kind
     * @param anno2 an initialization qualifier
     * @param qual2 a qualifier kind
     * @return the glb of anno1 and anno2
     */
    protected @Nullable AnnotationMirror greatestLowerBoundInitialization(
        AnnotationMirror anno1, QualifierKind qual1, AnnotationMirror anno2, QualifierKind qual2) {
      if (!isInitializationAnnotation(anno1) || !isInitializationAnnotation(anno2)) {
        return null;
      }

      // Handle the case where one is a subtype of the other.
      if (isSubtypeInitialization(anno1, qual1, anno2, qual2)) {
        return anno1;
      } else if (isSubtypeInitialization(anno2, qual2, anno1, qual1)) {
        return anno2;
      }
      boolean unknowninit1 = isUnknownInitialization(anno1);
      boolean unknowninit2 = isUnknownInitialization(anno2);
      boolean underinit1 = isUnderInitialization(anno1);
      boolean underinit2 = isUnderInitialization(anno2);

      // Handle @Initialized.
      if (isInitialized(anno1)) {
        assert underinit2;
        return FBCBOTTOM;
      } else if (isInitialized(anno2)) {
        assert underinit1;
        return FBCBOTTOM;
      }

      TypeMirror typeFrame =
          TypesUtils.greatestLowerBound(
              getTypeFrameFromAnnotation(anno1), getTypeFrameFromAnnotation(anno2), processingEnv);
      if (typeFrame.getKind() == TypeKind.ERROR || typeFrame.getKind() == TypeKind.INTERSECTION) {
        return FBCBOTTOM;
      }

      if (underinit1 && underinit2) {
        return createUnderInitializationAnnotation(typeFrame);
      }

      assert (unknowninit1 || underinit1) && (unknowninit2 || underinit2);
      return createUnderInitializationAnnotation(typeFrame);
    }
  }

  @Override
  protected ParameterizedExecutableType methodFromUse(
      ExpressionTree tree,
      ExecutableElement methodElt,
      AnnotatedTypeMirror receiverType,
      boolean inferTypeArgs) {
    ParameterizedExecutableType x =
        super.methodFromUse(tree, methodElt, receiverType, inferTypeArgs);
    if (tree.getKind() == Kind.MEMBER_REFERENCE
        && ((MemberReferenceTree) tree).getMode() == ReferenceMode.NEW) {
      x.executableType.getReturnType().replaceAnnotation(INITIALIZED);
    }
    return x;
  }
}
