@Override
  public PsiType[] guessContainerElementType(PsiExpression containerExpr, TextRange rangeToIgnore) {
    HashSet<PsiType> typesSet = new HashSet<>();

    PsiType type = containerExpr.getType();
    PsiType elemType;
    if ((elemType = getGenericElementType(type)) != null) return new PsiType[] {elemType};

    if (containerExpr instanceof PsiReferenceExpression) {
      PsiElement refElement = ((PsiReferenceExpression) containerExpr).resolve();
      if (refElement instanceof PsiVariable) {

        PsiFile file = refElement.getContainingFile();
        if (file == null) {
          file = containerExpr.getContainingFile(); // implicit variable in jsp
        }
        HashSet<PsiVariable> checkedVariables = new HashSet<>();
        addTypesByVariable(
            typesSet,
            (PsiVariable) refElement,
            file,
            checkedVariables,
            CHECK_USAGE | CHECK_DOWN,
            rangeToIgnore);
        checkedVariables.clear();
        addTypesByVariable(
            typesSet, (PsiVariable) refElement, file, checkedVariables, CHECK_UP, rangeToIgnore);
      }
    }

    return typesSet.toArray(PsiType.createArray(typesSet.size()));
  }
  @Nullable
  private static Map<PsiExpression, PsiType> buildDataflowTypeMap(PsiExpression forPlace) {
    PsiElement scope = DfaPsiUtil.getTopmostBlockInSameClass(forPlace);
    if (scope == null) {
      PsiFile file = forPlace.getContainingFile();
      if (!(file instanceof PsiCodeFragment)) {
        return Collections.emptyMap();
      }

      scope = file;
    }

    DataFlowRunner runner =
        new DataFlowRunner() {
          @NotNull
          @Override
          protected DfaMemoryState createMemoryState() {
            return new ExpressionTypeMemoryState(getFactory());
          }
        };

    final ExpressionTypeInstructionVisitor visitor = new ExpressionTypeInstructionVisitor(forPlace);
    if (runner.analyzeMethod(scope, visitor) == RunnerResult.OK) {
      return visitor.getResult();
    }
    return null;
  }
  private static Map<PsiExpression, PsiType> getAllTypeCasts(PsiExpression forPlace) {
    assert forPlace.isValid();
    final int start = forPlace.getTextRange().getStartOffset();
    final Map<PsiExpression, PsiType> allCasts =
        new THashMap<>(ExpressionTypeMemoryState.EXPRESSION_HASHING_STRATEGY);
    getTopmostBlock(forPlace)
        .accept(
            new JavaRecursiveElementWalkingVisitor() {
              @Override
              public void visitTypeCastExpression(PsiTypeCastExpression expression) {
                final PsiType castType = expression.getType();
                final PsiExpression operand = expression.getOperand();
                if (operand != null && castType != null) {
                  allCasts.put(operand, castType);
                }
                super.visitTypeCastExpression(expression);
              }

              @Override
              public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
                final PsiTypeElement castType = expression.getCheckType();
                final PsiExpression operand = expression.getOperand();
                if (castType != null) {
                  allCasts.put(operand, castType.getType());
                }
                super.visitInstanceOfExpression(expression);
              }

              @Override
              public void visitElement(PsiElement element) {
                if (element.getTextRange().getStartOffset() > start) {
                  return;
                }

                super.visitElement(element);
              }
            });
    return allCasts;
  }
  private void addExprTypesByDerivedClasses(LinkedHashSet<PsiType> set, PsiExpression expr) {
    PsiType type = expr.getType();
    if (!(type instanceof PsiClassType)) return;
    PsiClass refClass = PsiUtil.resolveClassInType(type);
    if (refClass == null) return;

    PsiManager manager = PsiManager.getInstance(myProject);
    PsiElementProcessor.CollectElementsWithLimit<PsiClass> processor =
        new PsiElementProcessor.CollectElementsWithLimit<>(5);
    ClassInheritorsSearch.search(refClass).forEach(new PsiElementProcessorAdapter<>(processor));
    if (processor.isOverflow()) return;

    for (PsiClass derivedClass : processor.getCollection()) {
      if (derivedClass instanceof PsiAnonymousClass) continue;
      PsiType derivedType =
          JavaPsiFacade.getInstance(manager.getProject())
              .getElementFactory()
              .createType(derivedClass);
      set.add(derivedType);
    }
  }
  private void addTypesByVariable(
      HashSet<PsiType> typesSet,
      PsiVariable var,
      PsiFile scopeFile,
      HashSet<PsiVariable> checkedVariables,
      int flags,
      TextRange rangeToIgnore) {
    if (!checkedVariables.add(var)) return;
    // System.out.println("analyzing usages of " + var + " in file " + scopeFile);
    SearchScope searchScope = new LocalSearchScope(scopeFile);

    if (BitUtil.isSet(flags, CHECK_USAGE) || BitUtil.isSet(flags, CHECK_DOWN)) {
      for (PsiReference varRef : ReferencesSearch.search(var, searchScope, false)) {
        PsiElement ref = varRef.getElement();

        if (BitUtil.isSet(flags, CHECK_USAGE)) {
          PsiType type = guessElementTypeFromReference(myMethodPatternMap, ref, rangeToIgnore);
          if (type != null && !(type instanceof PsiPrimitiveType)) {
            typesSet.add(type);
          }
        }

        if (BitUtil.isSet(flags, CHECK_DOWN)) {
          if (ref.getParent() instanceof PsiExpressionList
              && ref.getParent().getParent() instanceof PsiMethodCallExpression) { // TODO : new
            PsiExpressionList list = (PsiExpressionList) ref.getParent();
            PsiExpression[] args = list.getExpressions();
            int argIndex = -1;
            for (int j = 0; j < args.length; j++) {
              PsiExpression arg = args[j];
              if (arg.equals(ref)) {
                argIndex = j;
                break;
              }
            }

            PsiMethodCallExpression methodCall = (PsiMethodCallExpression) list.getParent();
            PsiMethod method = (PsiMethod) methodCall.getMethodExpression().resolve();
            if (method != null) {
              PsiParameter[] parameters = method.getParameterList().getParameters();
              if (argIndex < parameters.length) {
                addTypesByVariable(
                    typesSet,
                    parameters[argIndex],
                    method.getContainingFile(),
                    checkedVariables,
                    flags | CHECK_USAGE,
                    rangeToIgnore);
              }
            }
          }
        }
      }
    }

    if (BitUtil.isSet(flags, CHECK_UP)) {
      if (var instanceof PsiParameter
          && var.getParent() instanceof PsiParameterList
          && var.getParent().getParent() instanceof PsiMethod) {
        PsiParameterList list = (PsiParameterList) var.getParent();
        PsiParameter[] parameters = list.getParameters();
        int argIndex = -1;
        for (int i = 0; i < parameters.length; i++) {
          PsiParameter parameter = parameters[i];
          if (parameter.equals(var)) {
            argIndex = i;
            break;
          }
        }

        PsiMethod method = (PsiMethod) var.getParent().getParent();
        // System.out.println("analyzing usages of " + method + " in file " + scopeFile);
        for (PsiReference methodRef : ReferencesSearch.search(method, searchScope, false)) {
          PsiElement ref = methodRef.getElement();
          if (ref.getParent() instanceof PsiMethodCallExpression) {
            PsiMethodCallExpression methodCall = (PsiMethodCallExpression) ref.getParent();
            PsiExpression[] args = methodCall.getArgumentList().getExpressions();
            if (args.length <= argIndex) continue;
            PsiExpression arg = args[argIndex];
            if (arg instanceof PsiReferenceExpression) {
              PsiElement refElement = ((PsiReferenceExpression) arg).resolve();
              if (refElement instanceof PsiVariable) {
                addTypesByVariable(
                    typesSet,
                    (PsiVariable) refElement,
                    scopeFile,
                    checkedVariables,
                    flags | CHECK_USAGE,
                    rangeToIgnore);
              }
            }
            // TODO : constructor
          }
        }
      }
    }
  }