/**
   * Utility method to obtain annotations of a specific type from the supplied PsiModifierListOwner.
   * For optimization reasons, this method only looks at elements of type java.lang.String.
   *
   * <p>The parameter <code>allowIndirect</code> determines if the method should look for indirect
   * annotations, i.e. annotations which have themselves been annotated by the supplied annotation
   * name. Currently, this only allows one level of indirection and returns an array of
   * [base-annotation, indirect annotation]
   *
   * <p>The <code>annotationName</code> parameter is a pair of the target annotation class' fully
   * qualified name as a String and as a Set. This is done for performance reasons because the Set
   * is required by the {@link com.intellij.codeInsight.AnnotationUtil} utility class and allows to
   * avoid unnecessary object constructions.
   */
  @NotNull
  public static PsiAnnotation[] getAnnotationFrom(
      PsiModifierListOwner owner,
      Pair<String, ? extends Set<String>> annotationName,
      boolean allowIndirect,
      boolean inHierarchy) {
    if (!PsiUtilEx.isLanguageAnnotationTarget(owner)) return PsiAnnotation.EMPTY_ARRAY;

    return getAnnotationsFromImpl(owner, annotationName, allowIndirect, inHierarchy);
  }
  /**
   * Determines the PsiModifierListOwner for the passed element depending of the specified
   * LookupType. The LookupType decides whether to prefer the element a reference expressions
   * resolves to, or the element that is implied by the usage context ("expected type").
   */
  @Nullable
  public static PsiModifierListOwner getAnnotatedElementFor(
      @Nullable PsiElement element, LookupType type) {
    while (element != null) {
      if (type == LookupType.PREFER_DECLARATION || type == LookupType.DECLARATION_ONLY) {
        if (element instanceof PsiReferenceExpression) {
          final PsiElement e = ((PsiReferenceExpression) element).resolve();
          if (e instanceof PsiModifierListOwner) {
            return (PsiModifierListOwner) e;
          }
          if (type == LookupType.DECLARATION_ONLY) {
            return null;
          }
        }
      }
      element = ContextComputationProcessor.getTopLevelInjectionTarget(element);
      final PsiElement parent = element.getParent();

      if (element instanceof PsiAssignmentExpression
          && ((PsiAssignmentExpression) element).getOperationTokenType() == JavaTokenType.PLUSEQ) {
        element = ((PsiAssignmentExpression) element).getLExpression();
        continue;
      } else if (parent instanceof PsiAssignmentExpression) {
        final PsiAssignmentExpression p = (PsiAssignmentExpression) parent;
        if (p.getRExpression() == element) {
          element = p.getLExpression();
          continue;
        }
      } else if (parent instanceof PsiReturnStatement) {
        final PsiMethod m = PsiTreeUtil.getParentOfType(parent, PsiMethod.class);
        if (m != null) {
          return m;
        }
      } else if (parent instanceof PsiModifierListOwner) {
        return (PsiModifierListOwner) parent;
      } else if (parent instanceof PsiArrayInitializerMemberValue) {
        final PsiArrayInitializerMemberValue value = (PsiArrayInitializerMemberValue) parent;
        final PsiElement pair = value.getParent();
        if (pair instanceof PsiNameValuePair) {
          return AnnotationUtil.getAnnotationMethod((PsiNameValuePair) pair);
        }
      } else if (parent instanceof PsiNameValuePair) {
        return AnnotationUtil.getAnnotationMethod((PsiNameValuePair) parent);
      } else {
        return PsiUtilEx.getParameterForArgument(element);
      }

      // If no annotation has been found through the usage context, check if the element
      // (i.e. the element the reference refers to) is annotated itself
      if (type != LookupType.DECLARATION_ONLY) {
        if (element instanceof PsiReferenceExpression) {
          final PsiElement e = ((PsiReferenceExpression) element).resolve();
          if (e instanceof PsiModifierListOwner) {
            return (PsiModifierListOwner) e;
          }
        }
      }
      return null;
    }
    return null;
  }