package org.checkerframework.framework.util.defaults;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.ProcessingEnvironment;
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.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.util.Elements;
import org.checkerframework.checker.interning.qual.FindDistinct;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.AnnotatedFor;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
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.AnnotatedTypeMirror.AnnotatedNoType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedTypeVariable;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedUnionType;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedWildcardType;
import org.checkerframework.framework.type.GenericAnnotatedTypeFactory;
import org.checkerframework.framework.type.QualifierHierarchy;
import org.checkerframework.framework.type.visitor.AnnotatedTypeScanner;
import org.checkerframework.framework.util.AnnotatedTypes;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.AnnotationMirrorSet;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.ElementUtils;
import org.checkerframework.javacutil.TreeUtils;
import org.checkerframework.javacutil.TypesUtils;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.StringsPlume;

/**
 * Determines the default qualifiers on a type. Default qualifiers are specified via the {@link
 * org.checkerframework.framework.qual.DefaultQualifier} annotation.
 *
 * @see org.checkerframework.framework.qual.DefaultQualifier
 */
public class QualifierDefaults {

  // TODO add visitor state to get the default annotations from the top down?
  // TODO apply from package elements also
  // TODO try to remove some dependencies (e.g. on factory)

  /**
   * This field indicates whether or not a default should be applied to type vars located in the
   * type being defaulted. This should only ever be true when the type variable is a local variable,
   * non-component use, i.e.
   *
   * <pre>{@code
   * <T> void method(@NOT_HERE T tIn) {
   *     T t = tIn;
   * }
   * }</pre>
   *
   * The local variable T will be defaulted in order to allow dataflow to refine T. This variable
   * will be false if dataflow is not in use.
   */
  private boolean applyToTypeVar = false;

  /** Element utilities to use. */
  private final Elements elements;

  /** The value() element/field of a @DefaultQualifier annotation. */
  protected final ExecutableElement defaultQualifierValueElement;

  /** The locations() element/field of a @DefaultQualifier annotation. */
  protected final ExecutableElement defaultQualifierLocationsElement;

  /** The value() element/field of a @DefaultQualifier.List annotation. */
  protected final ExecutableElement defaultQualifierListValueElement;

  /** AnnotatedTypeFactory to use. */
  private final AnnotatedTypeFactory atypeFactory;

  /** Defaults for checked code. */
  private final DefaultSet checkedCodeDefaults = new DefaultSet();

  /** Defaults for unchecked code. */
  private final DefaultSet uncheckedCodeDefaults = new DefaultSet();

  /** Size for caches. */
  private static final int CACHE_SIZE = 300;

  /** Mapping from an Element to the bound type. */
  protected final Map<Element, BoundType> elementToBoundType =
      CollectionsPlume.createLruCache(CACHE_SIZE);

  /**
   * Defaults that apply for a certain Element. On the one hand this is used for caching (an earlier
   * name for the field was "qualifierCache"). It can also be used by type systems to set defaults
   * for certain Elements.
   */
  private final IdentityHashMap<Element, DefaultSet> elementDefaults = new IdentityHashMap<>();

  /** A mapping of Element &rarr; Whether or not that element is AnnotatedFor this type system. */
  private final IdentityHashMap<Element, Boolean> elementAnnotatedFors = new IdentityHashMap<>();

  /** CLIMB locations whose standard default is top for a given type system. */
  public static final List<TypeUseLocation> STANDARD_CLIMB_DEFAULTS_TOP =
      Collections.unmodifiableList(
          Arrays.asList(
              TypeUseLocation.LOCAL_VARIABLE,
              TypeUseLocation.RESOURCE_VARIABLE,
              TypeUseLocation.EXCEPTION_PARAMETER,
              TypeUseLocation.IMPLICIT_UPPER_BOUND));

  /** CLIMB locations whose standard default is bottom for a given type system. */
  public static final List<TypeUseLocation> STANDARD_CLIMB_DEFAULTS_BOTTOM =
      Collections.unmodifiableList(Arrays.asList(TypeUseLocation.IMPLICIT_LOWER_BOUND));

  /** List of TypeUseLocations that are valid for unchecked code defaults. */
  private static final List<TypeUseLocation> validUncheckedCodeDefaultLocations =
      Collections.unmodifiableList(
          Arrays.asList(
              TypeUseLocation.FIELD,
              TypeUseLocation.PARAMETER,
              TypeUseLocation.RETURN,
              TypeUseLocation.RECEIVER,
              TypeUseLocation.UPPER_BOUND,
              TypeUseLocation.LOWER_BOUND,
              TypeUseLocation.OTHERWISE,
              TypeUseLocation.ALL));

  /** Standard unchecked default locations that should be top. */
  // Fields are defaulted to top so that warnings are issued at field reads, which we believe are
  // more common than field writes. Future work is to specify different defaults for field reads
  // and field writes.  (When a field is written to, its type should be bottom.)
  public static final List<TypeUseLocation> STANDARD_UNCHECKED_DEFAULTS_TOP =
      Collections.unmodifiableList(
          Arrays.asList(
              TypeUseLocation.RETURN, TypeUseLocation.FIELD, TypeUseLocation.UPPER_BOUND));

  /** Standard unchecked default locations that should be bottom. */
  public static final List<TypeUseLocation> STANDARD_UNCHECKED_DEFAULTS_BOTTOM =
      Collections.unmodifiableList(
          Arrays.asList(TypeUseLocation.PARAMETER, TypeUseLocation.LOWER_BOUND));

  /** True if conservative defaults should be used in unannotated source code. */
  private final boolean useConservativeDefaultsSource;

  /** True if conservative defaults should be used for bytecode. */
  private final boolean useConservativeDefaultsBytecode;

  /**
   * Returns an array of locations that are valid for the unchecked value defaults. These are simply
   * by syntax, since an entire file is typechecked, it is not possible for local variables to be
   * unchecked.
   */
  public static List<TypeUseLocation> validLocationsForUncheckedCodeDefaults() {
    return validUncheckedCodeDefaultLocations;
  }

  /**
   * @param elements interface to Element data in the current processing environment
   * @param atypeFactory an annotation factory, used to get annotations by name
   */
  public QualifierDefaults(Elements elements, AnnotatedTypeFactory atypeFactory) {
    this.elements = elements;
    this.atypeFactory = atypeFactory;
    this.useConservativeDefaultsBytecode =
        atypeFactory.getChecker().useConservativeDefault("bytecode");
    this.useConservativeDefaultsSource = atypeFactory.getChecker().useConservativeDefault("source");
    ProcessingEnvironment processingEnv = atypeFactory.getProcessingEnv();
    this.defaultQualifierValueElement =
        TreeUtils.getMethod(DefaultQualifier.class, "value", 0, processingEnv);
    this.defaultQualifierLocationsElement =
        TreeUtils.getMethod(DefaultQualifier.class, "locations", 0, processingEnv);
    this.defaultQualifierListValueElement =
        TreeUtils.getMethod(DefaultQualifier.List.class, "value", 0, processingEnv);
  }

  @Override
  public String toString() {
    // displays the checked and unchecked code defaults
    return StringsPlume.joinLines(
        "Checked code defaults: ",
        StringsPlume.joinLines(checkedCodeDefaults),
        "Unchecked code defaults: ",
        StringsPlume.joinLines(uncheckedCodeDefaults),
        "useConservativeDefaultsSource: " + useConservativeDefaultsSource,
        "useConservativeDefaultsBytecode: " + useConservativeDefaultsBytecode);
  }

  /**
   * Check that a default with TypeUseLocation OTHERWISE or ALL is specified.
   *
   * @return whether we found a Default with location OTHERWISE or ALL
   */
  public boolean hasDefaultsForCheckedCode() {
    for (Default def : checkedCodeDefaults) {
      if (def.location == TypeUseLocation.OTHERWISE || def.location == TypeUseLocation.ALL) {
        return true;
      }
    }
    return false;
  }

  /** Add standard unchecked defaults that do not conflict with previously added defaults. */
  public void addUncheckedStandardDefaults() {
    QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy();
    AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations();
    AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations();

    for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_TOP) {
      // Only add standard defaults in locations where a default has not be specified
      for (AnnotationMirror top : tops) {
        if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, top, loc)) {
          addUncheckedCodeDefault(top, loc);
        }
      }
    }

    for (TypeUseLocation loc : STANDARD_UNCHECKED_DEFAULTS_BOTTOM) {
      for (AnnotationMirror bottom : bottoms) {
        // Only add standard defaults in locations where a default has not be specified
        if (!conflictsWithExistingDefaults(uncheckedCodeDefaults, bottom, loc)) {
          addUncheckedCodeDefault(bottom, loc);
        }
      }
    }
  }

  /** Add standard CLIMB defaults that do not conflict with previously added defaults. */
  public void addClimbStandardDefaults() {
    QualifierHierarchy qualHierarchy = this.atypeFactory.getQualifierHierarchy();
    AnnotationMirrorSet tops = qualHierarchy.getTopAnnotations();
    AnnotationMirrorSet bottoms = qualHierarchy.getBottomAnnotations();

    for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_TOP) {
      for (AnnotationMirror top : tops) {
        if (!conflictsWithExistingDefaults(checkedCodeDefaults, top, loc)) {
          // Only add standard defaults in locations where a default has not been
          // specified
          addCheckedCodeDefault(top, loc);
        }
      }
    }

    for (TypeUseLocation loc : STANDARD_CLIMB_DEFAULTS_BOTTOM) {
      for (AnnotationMirror bottom : bottoms) {
        if (!conflictsWithExistingDefaults(checkedCodeDefaults, bottom, loc)) {
          // Only add standard defaults in locations where a default has not been
          // specified
          addCheckedCodeDefault(bottom, loc);
        }
      }
    }
  }

  /**
   * Adds a default annotation. A programmer may override this by writing the @DefaultQualifier
   * annotation on an element.
   *
   * @param absoluteDefaultAnno the default annotation mirror
   * @param location the type use location
   */
  public void addCheckedCodeDefault(
      AnnotationMirror absoluteDefaultAnno, TypeUseLocation location) {
    checkDuplicates(checkedCodeDefaults, absoluteDefaultAnno, location);
    checkedCodeDefaults.add(new Default(absoluteDefaultAnno, location));
  }

  /**
   * Add a default annotation for unchecked elements.
   *
   * @param uncheckedDefaultAnno the default annotation mirror
   * @param location the type use location
   */
  public void addUncheckedCodeDefault(
      AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) {
    checkDuplicates(uncheckedCodeDefaults, uncheckedDefaultAnno, location);
    checkIsValidUncheckedCodeLocation(uncheckedDefaultAnno, location);

    uncheckedCodeDefaults.add(new Default(uncheckedDefaultAnno, location));
  }

  /** Sets the default annotation for unchecked elements, with specific locations. */
  public void addUncheckedCodeDefaults(
      AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) {
    for (TypeUseLocation location : locations) {
      addUncheckedCodeDefault(absoluteDefaultAnno, location);
    }
  }

  public void addCheckedCodeDefaults(
      AnnotationMirror absoluteDefaultAnno, TypeUseLocation[] locations) {
    for (TypeUseLocation location : locations) {
      addCheckedCodeDefault(absoluteDefaultAnno, location);
    }
  }

  /**
   * Sets the default annotations for a certain Element.
   *
   * @param elem the scope to set the default within
   * @param elementDefaultAnno the default to set
   * @param location the location to apply the default to
   */
  public void addElementDefault(
      Element elem, AnnotationMirror elementDefaultAnno, TypeUseLocation location) {
    DefaultSet prevset = elementDefaults.get(elem);
    if (prevset != null) {
      checkDuplicates(prevset, elementDefaultAnno, location);
    } else {
      prevset = new DefaultSet();
    }
    prevset.add(new Default(elementDefaultAnno, location));
    elementDefaults.put(elem, prevset);
  }

  private void checkIsValidUncheckedCodeLocation(
      AnnotationMirror uncheckedDefaultAnno, TypeUseLocation location) {
    boolean isValidUntypeLocation = false;
    for (TypeUseLocation validLoc : validLocationsForUncheckedCodeDefaults()) {
      if (location == validLoc) {
        isValidUntypeLocation = true;
        break;
      }
    }

    if (!isValidUntypeLocation) {
      throw new BugInCF(
          "Invalid unchecked code default location: " + location + " -> " + uncheckedDefaultAnno);
    }
  }

  private void checkDuplicates(
      DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) {
    if (conflictsWithExistingDefaults(previousDefaults, newAnno, newLoc)) {
      throw new BugInCF(
          "Only one qualifier from a hierarchy can be the default. Existing: "
              + previousDefaults
              + " and new: "
              + new Default(newAnno, newLoc));
    }
  }

  /**
   * Returns true if there are conflicts with existing defaults.
   *
   * @param previousDefaults the previous defaults
   * @param newAnno the new annotation
   * @param newLoc the location of the type use
   * @return true if there are conflicts with existing defaults
   */
  private boolean conflictsWithExistingDefaults(
      DefaultSet previousDefaults, AnnotationMirror newAnno, TypeUseLocation newLoc) {
    QualifierHierarchy qualHierarchy = atypeFactory.getQualifierHierarchy();

    for (Default previous : previousDefaults) {
      if (!AnnotationUtils.areSame(newAnno, previous.anno) && previous.location == newLoc) {
        AnnotationMirror previousTop = qualHierarchy.getTopAnnotation(previous.anno);
        if (qualHierarchy.isSubtypeQualifiersOnly(newAnno, previousTop)) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Applies default annotations to a type obtained from an {@link
   * javax.lang.model.element.Element}.
   *
   * @param elt the element from which the type was obtained
   * @param type the type to annotate
   */
  public void annotate(Element elt, AnnotatedTypeMirror type) {
    if (elt != null) {
      switch (elt.getKind()) {
        case FIELD:
        case LOCAL_VARIABLE:
        case PARAMETER:
        case RESOURCE_VARIABLE:
        case EXCEPTION_PARAMETER:
        case ENUM_CONSTANT:
          String varName = elt.getSimpleName().toString();
          ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
              .getDefaultForTypeAnnotator()
              .defaultTypeFromName(type, varName);

          break;

        case METHOD:
          String methodName = elt.getSimpleName().toString();
          AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) type).getReturnType();
          ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
              .getDefaultForTypeAnnotator()
              .defaultTypeFromName(returnType, methodName);

          break;

        default:
          break;
      }
    }

    applyDefaultsElement(elt, type);
  }

  /**
   * Applies default annotations to a type given a {@link com.sun.source.tree.Tree}.
   *
   * @param tree the tree from which the type was obtained
   * @param type the type to annotate
   */
  public void annotate(Tree tree, AnnotatedTypeMirror type) {
    applyDefaults(tree, type);
  }

  /**
   * Determines the nearest enclosing element for a tree by climbing the tree toward the root and
   * obtaining the element for the first declaration (variable, method, or class) that encloses the
   * tree. Initializers of local variables are handled in a special way: within an initializer we
   * look for the DefaultQualifier(s) annotation and keep track of the previously visited tree.
   * TODO: explain the behavior better.
   *
   * @param tree the tree
   * @return the nearest enclosing element for a tree
   */
  private @Nullable Element nearestEnclosingExceptLocal(Tree tree) {
    TreePath path = atypeFactory.getPath(tree);
    if (path == null) {
      Element element = atypeFactory.getEnclosingElementForArtificialTree(tree);
      if (element != null) {
        return element;
      } else {
        return TreeUtils.elementFromTree(tree);
      }
    }

    Tree prev = null;

    for (Tree t : path) {
      switch (TreeUtils.getKindRecordAsClass(t)) {
        case ANNOTATED_TYPE:
        case ANNOTATION:
          // If the tree is in an annotation, then there is no relevant scope.
          return null;
        case VARIABLE:
          VariableTree vtree = (VariableTree) t;
          ExpressionTree vtreeInit = vtree.getInitializer();
          @SuppressWarnings("interning:not.interned") // check cached value
          boolean sameAsPrev = (vtreeInit != null && prev == vtreeInit);
          if (sameAsPrev) {
            Element elt = TreeUtils.elementFromDeclaration((VariableTree) t);
            AnnotationMirror d = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class);
            AnnotationMirror ds = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class);

            if (d == null && ds == null) {
              break;
            }
          }
          if (prev != null && prev.getKind() == Tree.Kind.MODIFIERS) {
            // Annotations are modifiers. We do not want to apply the local variable
            // default to annotations. Without this, test fenum/TestSwitch failed,
            // because the default for an argument became incompatible with the declared
            // type.
            break;
          }
          return TreeUtils.elementFromDeclaration((VariableTree) t);
        case METHOD:
          return TreeUtils.elementFromDeclaration((MethodTree) t);
        case CLASS: // Including RECORD
        case ENUM:
        case INTERFACE:
        case ANNOTATION_TYPE:
          return TreeUtils.elementFromDeclaration((ClassTree) t);
        default: // Do nothing.
      }
      prev = t;
    }

    return null;
  }

  /**
   * Applies default annotations to a type. A {@link com.sun.source.tree.Tree} determines the
   * appropriate scope for defaults.
   *
   * <p>For instance, if the tree is associated with a declaration (e.g., it's the use of a field,
   * or a method invocation), defaults in the scope of the <i>declaration</i> are used; if the tree
   * is not associated with a declaration (e.g., a typecast), defaults in the scope of the tree are
   * used.
   *
   * @param tree the tree associated with the type
   * @param type the type to which defaults will be applied
   * @see #applyDefaultsElement(javax.lang.model.element.Element,
   *     org.checkerframework.framework.type.AnnotatedTypeMirror)
   */
  private void applyDefaults(Tree tree, AnnotatedTypeMirror type) {

    // The location to take defaults from.
    Element elt;
    switch (tree.getKind()) {
      case MEMBER_SELECT:
        elt = TreeUtils.elementFromUse((MemberSelectTree) tree);
        break;

      case IDENTIFIER:
        elt = TreeUtils.elementFromUse((IdentifierTree) tree);
        if (ElementUtils.isTypeDeclaration(elt)) {
          // If the identifier is a type, then use the scope of the tree.
          elt = nearestEnclosingExceptLocal(tree);
        }
        break;

      case METHOD_INVOCATION:
        elt = TreeUtils.elementFromUse((MethodInvocationTree) tree);
        break;

        // TODO cases for array access, etc. -- every expression tree
        // (The above probably means that we should use defaults in the
        // scope of the declaration of the array.  Is that right?  -MDE)

      default:
        // If no associated symbol was found, use the tree's (lexical) scope.
        elt = nearestEnclosingExceptLocal(tree);
        // elt = nearestEnclosing(tree);
    }
    // System.out.println("applyDefaults on tree " + tree +
    //        " gives elt: " + elt + "(" + elt.getKind() + ")");

    boolean defaultTypeVarLocals =
        (atypeFactory instanceof GenericAnnotatedTypeFactory<?, ?, ?, ?>)
            && ((GenericAnnotatedTypeFactory<?, ?, ?, ?>) atypeFactory)
                .getShouldDefaultTypeVarLocals();
    applyToTypeVar =
        defaultTypeVarLocals
            && elt != null
            && ElementUtils.isLocalVariable(elt)
            && type.getKind() == TypeKind.TYPEVAR;
    applyDefaultsElement(elt, type);
    applyToTypeVar = false;
  }

  /** The default {@code value} element for a @DefaultQualifier annotation. */
  private static final TypeUseLocation[] defaultQualifierValueDefault =
      new TypeUseLocation[] {org.checkerframework.framework.qual.TypeUseLocation.ALL};

  /**
   * Create a DefaultSet from a @DefaultQualifier annotation.
   *
   * @param dq a @DefaultQualifier annotation
   * @return a DefaultSet corresponding to the @DefaultQualifier annotation
   */
  private @Nullable DefaultSet fromDefaultQualifier(AnnotationMirror dq) {
    @SuppressWarnings("unchecked")
    Name cls = AnnotationUtils.getElementValueClassName(dq, defaultQualifierValueElement);
    AnnotationMirror anno = AnnotationBuilder.fromName(elements, cls);

    if (anno == null) {
      return null;
    }

    if (!atypeFactory.isSupportedQualifier(anno)) {
      anno = atypeFactory.canonicalAnnotation(anno);
    }

    if (atypeFactory.isSupportedQualifier(anno)) {
      TypeUseLocation[] locations =
          AnnotationUtils.getElementValueEnumArray(
              dq,
              defaultQualifierLocationsElement,
              TypeUseLocation.class,
              defaultQualifierValueDefault);
      DefaultSet ret = new DefaultSet();
      for (TypeUseLocation loc : locations) {
        ret.add(new Default(anno, loc));
      }
      return ret;
    } else {
      return null;
    }
  }

  private boolean isElementAnnotatedForThisChecker(Element elt) {
    boolean elementAnnotatedForThisChecker = false;

    if (elt == null) {
      throw new BugInCF("Call of QualifierDefaults.isElementAnnotatedForThisChecker with null");
    }

    if (elementAnnotatedFors.containsKey(elt)) {
      return elementAnnotatedFors.get(elt);
    }

    AnnotationMirror annotatedFor = atypeFactory.getDeclAnnotation(elt, AnnotatedFor.class);

    if (annotatedFor != null) {
      elementAnnotatedForThisChecker =
          atypeFactory.doesAnnotatedForApplyToThisChecker(annotatedFor);
    }

    if (!elementAnnotatedForThisChecker) {
      Element parent;
      if (elt.getKind() == ElementKind.PACKAGE) {
        // elt.getEnclosingElement() on a package is null; therefore,
        // use the dedicated method.
        parent = ElementUtils.parentPackage((PackageElement) elt, elements);
      } else {
        parent = elt.getEnclosingElement();
      }

      if (parent != null && isElementAnnotatedForThisChecker(parent)) {
        elementAnnotatedForThisChecker = true;
      }
    }

    if (atypeFactory.shouldCache
        && !atypeFactory.stubTypes.isParsing()
        && !atypeFactory.ajavaTypes.isParsing()) {
      elementAnnotatedFors.put(elt, elementAnnotatedForThisChecker);
    }

    return elementAnnotatedForThisChecker;
  }

  /**
   * Returns the defaults that apply to the given Element, considering defaults from enclosing
   * Elements.
   *
   * @param elt the element
   * @return the defaults
   */
  private DefaultSet defaultsAt(Element elt) {
    if (elt == null) {
      return DefaultSet.EMPTY;
    }

    if (elementDefaults.containsKey(elt)) {
      return elementDefaults.get(elt);
    }

    DefaultSet qualifiers = null;

    {
      AnnotationMirror dqAnno = atypeFactory.getDeclAnnotation(elt, DefaultQualifier.class);

      if (dqAnno != null) {
        qualifiers = new DefaultSet();
        Set<Default> p = fromDefaultQualifier(dqAnno);

        if (p != null) {
          qualifiers.addAll(p);
        }
      }
    }

    {
      AnnotationMirror dqListAnno =
          atypeFactory.getDeclAnnotation(elt, DefaultQualifier.List.class);
      if (dqListAnno != null) {
        if (qualifiers == null) {
          qualifiers = new DefaultSet();
        }

        List<AnnotationMirror> values =
            AnnotationUtils.getElementValueArray(
                dqListAnno, defaultQualifierListValueElement, AnnotationMirror.class);
        for (AnnotationMirror dqAnno : values) {
          Set<Default> p = fromDefaultQualifier(dqAnno);
          if (p != null) {
            qualifiers.addAll(p);
          }
        }
      }
    }

    Element parent;
    if (elt.getKind() == ElementKind.PACKAGE) {
      parent = ElementUtils.parentPackage((PackageElement) elt, elements);
    } else {
      parent = elt.getEnclosingElement();
    }

    DefaultSet parentDefaults = defaultsAt(parent);
    if (qualifiers == null || qualifiers.isEmpty()) {
      qualifiers = parentDefaults;
    } else {
      qualifiers.addAll(parentDefaults);
    }

    if (qualifiers != null && !qualifiers.isEmpty()) {
      elementDefaults.put(elt, qualifiers);
      return qualifiers;
    } else {
      return DefaultSet.EMPTY;
    }
  }

  /**
   * Given an element, returns whether the conservative default should be applied for it. Handles
   * elements from bytecode or source code.
   *
   * @param annotationScope the element that the conservative default might apply to
   * @return whether the conservative default applies to the given element
   */
  public boolean applyConservativeDefaults(Element annotationScope) {
    if (annotationScope == null) {
      return false;
    }

    if (uncheckedCodeDefaults.isEmpty()) {
      return false;
    }

    // TODO: I would expect this:
    //   atypeFactory.isFromByteCode(annotationScope)) {
    // to work instead of the
    // isElementFromByteCode/declarationFromElement/isFromStubFile calls,
    // but it doesn't work correctly and tests fail.

    boolean isFromStubFile = atypeFactory.isFromStubFile(annotationScope);
    boolean isBytecode =
        ElementUtils.isElementFromByteCode(annotationScope)
            && atypeFactory.declarationFromElement(annotationScope) == null
            && !isFromStubFile;
    if (isBytecode) {
      return useConservativeDefaultsBytecode && !isElementAnnotatedForThisChecker(annotationScope);
    } else if (isFromStubFile) {
      // TODO: Types in stub files not annotated for a particular checker should be
      // treated as unchecked bytecode.  For now, all types in stub files are treated as
      // checked code. Eventually, @AnnotateFor(checker) will be programmatically added
      // to methods in stub files supplied via the @Stubfile annotation.  Stub files will
      // be treated like unchecked code except for methods in the scope for an @AnnotatedFor.
      return false;
    } else if (useConservativeDefaultsSource) {
      return !isElementAnnotatedForThisChecker(annotationScope);
    }
    return false;
  }

  /**
   * Applies default annotations to a type. Conservative defaults are applied first as appropriate,
   * followed by source code defaults.
   *
   * <p>For a discussion on the rules for application of source code and conservative defaults,
   * please see the linked manual sections.
   *
   * @param annotationScope the element representing the nearest enclosing default annotation scope
   *     for the type
   * @param type the type to which defaults will be applied
   * @checker_framework.manual #effective-qualifier The effective qualifier on a type (defaults and
   *     inference)
   * @checker_framework.manual #annotating-libraries Annotating libraries
   */
  private void applyDefaultsElement(Element annotationScope, AnnotatedTypeMirror type) {
    DefaultSet defaults = defaultsAt(annotationScope);
    DefaultApplierElement applier =
        createDefaultApplierElement(atypeFactory, annotationScope, type, applyToTypeVar);

    for (Default def : defaults) {
      applier.applyDefault(def);
    }

    if (applyConservativeDefaults(annotationScope)) {
      for (Default def : uncheckedCodeDefaults) {
        applier.applyDefault(def);
      }
    }

    for (Default def : checkedCodeDefaults) {
      applier.applyDefault(def);
    }
  }

  protected DefaultApplierElement createDefaultApplierElement(
      AnnotatedTypeFactory atypeFactory,
      Element annotationScope,
      AnnotatedTypeMirror type,
      boolean applyToTypeVar) {
    return new DefaultApplierElement(atypeFactory, annotationScope, type, applyToTypeVar);
  }

  /** A default applier element. */
  protected class DefaultApplierElement {

    /** The annotated type factory. */
    protected final AnnotatedTypeFactory atypeFactory;

    /** The scope of the default. */
    protected final Element scope;

    /** The type to which to apply the default. */
    protected final AnnotatedTypeMirror type;

    /** Location to which to apply the default. (Should only be set by the applyDefault method.) */
    protected TypeUseLocation location;

    /** The default element applier implementation. */
    protected final DefaultApplierElementImpl impl;

    /*
      Local type variables are defaulted to top when flow is turned on
      We only want to default the top level type variable (and not type variables that are nested
      in its bounds). E.g.,
        <T extends List<E>, E extends Object> void method() {
           T t;
        }
      We would like t to have its primary annotation defaulted but NOT the E inside its upper bound.
      we use referential equality with the top level type var to determine which ones are definite
      type uses, i.e. uses which can be defaulted
    */
    private final AnnotatedTypeVariable defaultableTypeVar;

    public DefaultApplierElement(
        AnnotatedTypeFactory atypeFactory,
        Element scope,
        AnnotatedTypeMirror type,
        boolean applyToTypeVar) {
      this.atypeFactory = atypeFactory;
      this.scope = scope;
      this.type = type;
      this.impl = new DefaultApplierElementImpl();
      this.defaultableTypeVar = applyToTypeVar ? (AnnotatedTypeVariable) type : null;
    }

    /**
     * Apply default to the type.
     *
     * @param def default to apply
     */
    public void applyDefault(Default def) {
      this.location = def.location;
      impl.visit(type, def.anno);
    }

    /**
     * Returns true if the given qualifier should be applied to the given type. Currently we do not
     * apply defaults to void types, packages, wildcards, and type variables.
     *
     * @param type type to which qual would be applied
     * @return true if this application should proceed
     */
    protected boolean shouldBeAnnotated(AnnotatedTypeMirror type, boolean applyToTypeVar) {

      return !(type == null
          // TODO: executables themselves should not be annotated
          // For some reason h1h2checker-tests fails with this.
          // || type.getKind() == TypeKind.EXECUTABLE
          || type.getKind() == TypeKind.NONE
          || type.getKind() == TypeKind.WILDCARD
          || (type.getKind() == TypeKind.TYPEVAR && !applyToTypeVar)
          || type instanceof AnnotatedNoType);
    }

    /**
     * Add the qualifier to the type if it does not already have an annotation in the same hierarchy
     * as qual.
     *
     * @param type type to add qual
     * @param qual annotation to add
     */
    protected void addAnnotation(AnnotatedTypeMirror type, AnnotationMirror qual) {
      // Add the default annotation, but only if no other annotation is present.
      if (type.getKind() != TypeKind.EXECUTABLE) {
        type.addMissingAnnotation(qual);
      }
    }

    protected class DefaultApplierElementImpl extends AnnotatedTypeScanner<Void, AnnotationMirror> {

      @Override
      public Void scan(@FindDistinct AnnotatedTypeMirror t, AnnotationMirror qual) {
        if (!shouldBeAnnotated(t, t == defaultableTypeVar)) {
          return super.scan(t, qual);
        }

        // Some defaults only apply to the top level type.
        boolean isTopLevelType = t == type;
        switch (location) {
          case FIELD:
            if (scope != null && scope.getKind() == ElementKind.FIELD && isTopLevelType) {
              addAnnotation(t, qual);
            }
            break;
          case LOCAL_VARIABLE:
            if (scope != null && scope.getKind() == ElementKind.LOCAL_VARIABLE && isTopLevelType) {
              // TODO: how do we determine that we are in a cast or instanceof type?
              addAnnotation(t, qual);
            }
            break;
          case RESOURCE_VARIABLE:
            if (scope != null
                && scope.getKind() == ElementKind.RESOURCE_VARIABLE
                && isTopLevelType) {
              addAnnotation(t, qual);
            }
            break;
          case EXCEPTION_PARAMETER:
            if (scope != null
                && scope.getKind() == ElementKind.EXCEPTION_PARAMETER
                && isTopLevelType) {
              addAnnotation(t, qual);
              if (t.getKind() == TypeKind.UNION) {
                AnnotatedUnionType aut = (AnnotatedUnionType) t;
                // Also apply the default to the alternative types
                for (AnnotatedDeclaredType anno : aut.getAlternatives()) {
                  addAnnotation(anno, qual);
                }
              }
            }
            break;
          case PARAMETER:
            if (scope != null && scope.getKind() == ElementKind.PARAMETER && isTopLevelType) {
              addAnnotation(t, qual);
            } else if (scope != null
                && (scope.getKind() == ElementKind.METHOD
                    || scope.getKind() == ElementKind.CONSTRUCTOR)
                && t.getKind() == TypeKind.EXECUTABLE
                && isTopLevelType) {

              for (AnnotatedTypeMirror atm : ((AnnotatedExecutableType) t).getParameterTypes()) {
                if (shouldBeAnnotated(atm, false)) {
                  addAnnotation(atm, qual);
                }
              }
            }
            break;
          case RECEIVER:
            if (scope != null
                && scope.getKind() == ElementKind.PARAMETER
                && isTopLevelType
                && scope.getSimpleName().contentEquals("this")) {
              // TODO: comparison against "this" is ugly, won't work
              // for all possible names for receiver parameter.
              // Comparison to Names._this might be a bit faster.
              addAnnotation(t, qual);
            } else if (scope != null
                && (scope.getKind() == ElementKind.METHOD)
                && t.getKind() == TypeKind.EXECUTABLE
                && isTopLevelType) {

              AnnotatedDeclaredType receiver = ((AnnotatedExecutableType) t).getReceiverType();
              if (shouldBeAnnotated(receiver, false)) {
                addAnnotation(receiver, qual);
              }
            }
            break;
          case RETURN:
            if (scope != null
                && scope.getKind() == ElementKind.METHOD
                && t.getKind() == TypeKind.EXECUTABLE
                && isTopLevelType) {
              AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType();
              if (shouldBeAnnotated(returnType, false)) {
                addAnnotation(returnType, qual);
              }
            }
            break;
          case CONSTRUCTOR_RESULT:
            if (scope != null
                && scope.getKind() == ElementKind.CONSTRUCTOR
                && t.getKind() == TypeKind.EXECUTABLE
                && isTopLevelType) {
              // This is the return type of a constructor declaration (not a
              // constructor invocation).
              AnnotatedTypeMirror returnType = ((AnnotatedExecutableType) t).getReturnType();
              if (shouldBeAnnotated(returnType, false)) {
                addAnnotation(returnType, qual);
              }
            }
            break;
          case IMPLICIT_LOWER_BOUND:
            if (isLowerBound && boundType.isOneOf(BoundType.UNBOUNDED, BoundType.UPPER)) {
              addAnnotation(t, qual);
            }
            break;
          case EXPLICIT_LOWER_BOUND:
            if (isLowerBound && boundType.isOneOf(BoundType.LOWER)) {
              addAnnotation(t, qual);
            }
            break;
          case LOWER_BOUND:
            if (isLowerBound) {
              addAnnotation(t, qual);
            }
            break;
          case IMPLICIT_UPPER_BOUND:
            if (isUpperBound && boundType.isOneOf(BoundType.UNBOUNDED, BoundType.LOWER)) {
              addAnnotation(t, qual);
            }
            break;
          case EXPLICIT_UPPER_BOUND:
            if (isUpperBound && boundType.isOneOf(BoundType.UPPER)) {
              addAnnotation(t, qual);
            }
            break;
          case UPPER_BOUND:
            if (this.isUpperBound) {
              addAnnotation(t, qual);
            }
            break;
          case OTHERWISE:
          case ALL:
            // TODO: forbid ALL if anything else was given.
            addAnnotation(t, qual);
            break;
          default:
            throw new BugInCF(
                "QualifierDefaults.DefaultApplierElement: unhandled location: " + location);
        }

        return super.scan(t, qual);
      }

      @Override
      public void reset() {
        super.reset();
        impl.isLowerBound = false;
        impl.isUpperBound = false;
        impl.boundType = BoundType.UNBOUNDED;
      }

      // are we currently defaulting the lower bound of a type variable or wildcard
      private boolean isLowerBound = false;

      // are we currently defaulting the upper bound of a type variable or wildcard
      private boolean isUpperBound = false;

      // the bound type of the current wildcard or type variable being defaulted
      private BoundType boundType = BoundType.UNBOUNDED;

      @Override
      public Void visitTypeVariable(AnnotatedTypeVariable type, AnnotationMirror qual) {
        if (visitedNodes.containsKey(type)) {
          return visitedNodes.get(type);
        }

        visitBounds(type, type.getUpperBound(), type.getLowerBound(), qual);
        return null;
      }

      @Override
      public Void visitWildcard(AnnotatedWildcardType type, AnnotationMirror qual) {
        if (visitedNodes.containsKey(type)) {
          return visitedNodes.get(type);
        }

        visitBounds(type, type.getExtendsBound(), type.getSuperBound(), qual);
        return null;
      }

      /**
       * Visit the bounds of a type variable or a wildcard and potentially apply qual to those
       * bounds. This method will also update the boundType, isLowerBound, and isUpperbound fields.
       */
      protected void visitBounds(
          AnnotatedTypeMirror boundedType,
          AnnotatedTypeMirror upperBound,
          AnnotatedTypeMirror lowerBound,
          AnnotationMirror qual) {

        boolean prevIsUpperBound = isUpperBound;
        boolean prevIsLowerBound = isLowerBound;
        BoundType prevBoundType = boundType;

        boundType = getBoundType(boundedType);

        try {
          isLowerBound = true;
          isUpperBound = false;
          scanAndReduce(lowerBound, qual, null);

          visitedNodes.put(type, null);

          isLowerBound = false;
          isUpperBound = true;
          scanAndReduce(upperBound, qual, null);

          visitedNodes.put(type, null);

        } finally {
          isUpperBound = prevIsUpperBound;
          isLowerBound = prevIsLowerBound;
          boundType = prevBoundType;
        }
      }
    }
  }

  /**
   * Specifies whether the type variable or wildcard has an explicit upper bound (UPPER), an
   * explicit lower bound (LOWER), or no explicit bounds (UNBOUNDED).
   */
  protected enum BoundType {

    /** Indicates an upper-bounded type variable or wildcard. */
    UPPER,

    /** Indicates a lower-bounded type variable or wildcard. */
    LOWER,

    /**
     * Neither bound is specified, BOTH are implicit. (If a type variable is declared in bytecode
     * and the type of the upper bound is Object, then the checker assumes that the bound was not
     * explicitly written in source code.)
     */
    UNBOUNDED;

    public boolean isOneOf(BoundType... choices) {
      for (BoundType choice : choices) {
        if (this == choice) {
          return true;
        }
      }

      return false;
    }
  }

  /**
   * Returns the boundType for type.
   *
   * @param type the type whose boundType is returned. type must be an AnnotatedWildcardType or
   *     AnnotatedTypeVariable.
   * @return the boundType for type
   */
  private BoundType getBoundType(AnnotatedTypeMirror type) {
    if (type instanceof AnnotatedTypeVariable) {
      return getTypeVarBoundType((AnnotatedTypeVariable) type);
    }

    if (type instanceof AnnotatedWildcardType) {
      return getWildcardBoundType((AnnotatedWildcardType) type);
    }

    throw new BugInCF("Unexpected type kind: type=" + type);
  }

  /**
   * Returns the bound type of the input typeVar.
   *
   * @param typeVar the type variable
   * @return the bound type of the input typeVar
   */
  private BoundType getTypeVarBoundType(AnnotatedTypeVariable typeVar) {
    return getTypeVarBoundType((TypeParameterElement) typeVar.getUnderlyingType().asElement());
  }

  /**
   * Returns the boundType (UPPER or UNBOUNDED) of the declaration of typeParamElem.
   *
   * @param typeParamElem the type parameter element
   * @return the boundType (UPPER or UNBOUNDED) of the declaration of typeParamElem
   */
  // Results are cached in {@link elementToBoundType}.
  private BoundType getTypeVarBoundType(TypeParameterElement typeParamElem) {
    BoundType prev = elementToBoundType.get(typeParamElem);
    if (prev != null) {
      return prev;
    }

    TreePath declaredTypeVarEle = atypeFactory.getTreeUtils().getPath(typeParamElem);
    Tree typeParamDecl = declaredTypeVarEle == null ? null : declaredTypeVarEle.getLeaf();

    final BoundType boundType;
    if (typeParamDecl == null) {
      // This is not only for elements from binaries, but also
      // when the compilation unit is no-longer available.
      if (typeParamElem.getBounds().size() == 1
          && TypesUtils.isObject(typeParamElem.getBounds().get(0))) {
        // If the bound was Object, then it may or may not have been explicitly written.
        // Assume that it was not.
        boundType = BoundType.UNBOUNDED;
      } else {
        // The bound is not Object, so it must have been explicitly written and thus the
        // type variable has an upper bound.
        boundType = BoundType.UPPER;
      }

    } else {
      if (typeParamDecl.getKind() == Tree.Kind.TYPE_PARAMETER) {
        TypeParameterTree tptree = (TypeParameterTree) typeParamDecl;

        List<? extends Tree> bnds = tptree.getBounds();
        if (bnds != null && !bnds.isEmpty()) {
          boundType = BoundType.UPPER;
        } else {
          boundType = BoundType.UNBOUNDED;
        }
      } else {
        throw new BugInCF(
            StringsPlume.joinLines(
                "Unexpected tree type for typeVar Element:",
                "typeParamElem=" + typeParamElem,
                typeParamDecl));
      }
    }

    elementToBoundType.put(typeParamElem, boundType);
    return boundType;
  }

  /**
   * Returns the BoundType of annotatedWildcard. If it is unbounded, use the type parameter to which
   * its an argument.
   *
   * @param wildcardType the annotated wildcard type
   * @return the BoundType of annotatedWildcard. If it is unbounded, use the type parameter to which
   *     its an argument
   */
  public BoundType getWildcardBoundType(AnnotatedWildcardType wildcardType) {
    if (AnnotatedTypes.hasNoExplicitBound(wildcardType)) {
      TypeParameterElement e = TypesUtils.wildcardToTypeParam(wildcardType.getUnderlyingType());
      if (e != null) {
        return getTypeVarBoundType(e);
      } else {
        return BoundType.UNBOUNDED;
      }
    } else if (AnnotatedTypes.hasExplicitSuperBound(wildcardType)) {
      return BoundType.LOWER;
    } else {
      return BoundType.UPPER;
    }
  }
}
